@theihtisham/agent-shadow-brain 7.0.0 → 7.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/README.md +36 -396
- package/dist/brain/auto-update.js +1 -1
- package/dist/brain/brain-portability.js +1 -1
- package/dist/brain/context-assembly.d.ts +58 -0
- package/dist/brain/context-assembly.d.ts.map +1 -0
- package/dist/brain/context-assembly.js +207 -0
- package/dist/brain/context-assembly.js.map +1 -0
- package/dist/brain/feature-settings.d.ts +53 -0
- package/dist/brain/feature-settings.d.ts.map +1 -0
- package/dist/brain/feature-settings.js +232 -0
- package/dist/brain/feature-settings.js.map +1 -0
- package/dist/brain/global-brain.d.ts +1 -0
- package/dist/brain/global-brain.d.ts.map +1 -1
- package/dist/brain/global-brain.js +3 -3
- package/dist/brain/global-brain.js.map +1 -1
- package/dist/brain/mcp-server.d.ts +4 -0
- package/dist/brain/mcp-server.d.ts.map +1 -1
- package/dist/brain/mcp-server.js +81 -5
- package/dist/brain/mcp-server.js.map +1 -1
- package/dist/brain/orchestrator.js +1 -1
- package/dist/brain/subconscious.d.ts +19 -24
- package/dist/brain/subconscious.d.ts.map +1 -1
- package/dist/brain/subconscious.js +77 -218
- package/dist/brain/subconscious.js.map +1 -1
- package/dist/brain/task-workflow.d.ts +22 -0
- package/dist/brain/task-workflow.d.ts.map +1 -0
- package/dist/brain/task-workflow.js +182 -0
- package/dist/brain/task-workflow.js.map +1 -0
- package/dist/brain/workflow-skills.d.ts +86 -0
- package/dist/brain/workflow-skills.d.ts.map +1 -0
- package/dist/brain/workflow-skills.js +514 -0
- package/dist/brain/workflow-skills.js.map +1 -0
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/context-cli.d.ts +3 -0
- package/dist/context-cli.d.ts.map +1 -0
- package/dist/context-cli.js +54 -0
- package/dist/context-cli.js.map +1 -0
- package/dist/dashboard/server.d.ts +1 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +32 -4
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts +1 -1
- package/dist/dashboard/v6-dashboard-html.d.ts.map +1 -1
- package/dist/dashboard/v6-dashboard-html.js +5 -27
- package/dist/dashboard/v6-dashboard-html.js.map +1 -1
- package/dist/dashboard/workspace-api.d.ts +10 -0
- package/dist/dashboard/workspace-api.d.ts.map +1 -0
- package/dist/dashboard/workspace-api.js +193 -0
- package/dist/dashboard/workspace-api.js.map +1 -0
- package/dist/dashboard/workspace-html.d.ts +2 -0
- package/dist/dashboard/workspace-html.d.ts.map +1 -0
- package/dist/dashboard/workspace-html.js +1555 -0
- package/dist/dashboard/workspace-html.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/workflow-cli.d.ts +3 -0
- package/dist/workflow-cli.d.ts.map +1 -0
- package/dist/workflow-cli.js +100 -0
- package/dist/workflow-cli.js.map +1 -0
- package/docs/WORKFLOW_INTEGRATION.md +105 -0
- package/docs/WORKSPACE.md +70 -0
- package/docs/assets/context-flow-v7.2.png +0 -0
- package/docs/assets/workspace-graph-v7.2.png +0 -0
- package/docs/assets/workspace-overview-v7.2.png +0 -0
- package/docs/launch/RELEASE_NOTES_v7.1.0.md +32 -0
- package/docs/launch/RELEASE_NOTES_v7.2.0.md +24 -0
- package/package.json +111 -207
- package/skills/shadow-debug/SKILL.md +16 -0
- package/skills/shadow-implement/SKILL.md +16 -0
- package/skills/shadow-release/SKILL.md +16 -0
- package/skills/shadow-research/SKILL.md +12 -0
- package/skills/shadow-review/SKILL.md +14 -0
- package/skills/shadow-workflow/SKILL.md +31 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const V6_DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\" data-navmode=\"simple\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=5.0\">\n<meta name=\"color-scheme\" content=\"dark light\">\n<title>Shadow Brain v7.0 \u2014 Hive Mind Control</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%230a0e1a'/><path d='M50 50 L24 30 M50 50 L76 30 M50 50 L24 70 M50 50 L76 70' stroke='%23ffffff' stroke-opacity='0.28' stroke-width='3'/><circle cx='50' cy='50' r='12' fill='%2318ffff'/><circle cx='24' cy='30' r='7' fill='%2310e68c'/><circle cx='76' cy='30' r='7' fill='%23ffb53f'/><circle cx='24' cy='70' r='7' fill='%23a855f7'/><circle cx='76' cy='70' r='7' fill='%23ec4899'/></svg>\">\n<link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n<script src=\"https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\"></script>\n<script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.min.js\"></script>\n<style>\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Design System Tokens */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n :root {\n /* Dark theme */\n --bg: #030509;\n --bg-elev-1: #090d17;\n --bg-elev-2: #10152a;\n --bg-elev-3: #18203b;\n --bg-glass: rgba(15, 20, 38, 0.55);\n --bg-glass-strong: rgba(22, 28, 52, 0.78);\n --border: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.14);\n --border-accent: rgba(24, 255, 255, 0.32);\n\n --text: #e8ecf4;\n --text-2: #a4afc4;\n --text-3: #64708a;\n --text-dim: #3e4762;\n\n --brand-cyan: #18ffff;\n --brand-purple: #a855f7;\n --brand-pink: #ec4899;\n --brand-gradient: linear-gradient(135deg, #18ffff 0%, #a855f7 50%, #ec4899 100%);\n --brand-gradient-soft: linear-gradient(135deg, rgba(24,255,255,0.15), rgba(168,85,247,0.15));\n\n --success: #10e68c;\n --warning: #ffb53f;\n --danger: #ff5864;\n --info: #4fb6ff;\n\n --radius-sm: 6px;\n --radius: 10px;\n --radius-lg: 14px;\n --radius-xl: 20px;\n\n --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);\n --shadow: 0 6px 24px -6px rgba(0,0,0,0.5), 0 0 0 1px var(--border);\n --shadow-lg: 0 18px 56px -12px rgba(0,0,0,0.6), 0 0 0 1px var(--border-strong);\n --shadow-glow: 0 0 40px rgba(24,255,255,0.15);\n --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0) 42%);\n\n --duration-fast: 120ms;\n --duration: 220ms;\n --duration-slow: 400ms;\n --easing: cubic-bezier(0.4, 0, 0.2, 1);\n --easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);\n\n --sidebar-w: 252px;\n --header-h: 64px;\n --ticker-h: 32px;\n\n --font-sans: 'Inter', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif;\n --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;\n }\n\n /* Light theme \u2014 clean, high-contrast, professional */\n html[data-theme=\"light\"] {\n --bg: #f7f8fb;\n --bg-elev-1: #ffffff;\n --bg-elev-2: #f2f4f8;\n --bg-elev-3: #e8ecf2;\n --bg-glass: rgba(255, 255, 255, 0.88);\n --bg-glass-strong: rgba(255, 255, 255, 0.96);\n --border: rgba(15, 23, 42, 0.10);\n --border-strong: rgba(15, 23, 42, 0.18);\n --border-accent: rgba(99, 102, 241, 0.38);\n --text: #0a0f1c;\n --text-2: #2b3648;\n --text-3: #55607a;\n --text-dim: #9ca3af;\n --brand-cyan: #0891b2;\n --brand-purple: #7c3aed;\n --brand-pink: #db2777;\n --brand-gradient: linear-gradient(135deg, #0891b2 0%, #7c3aed 55%, #db2777 100%);\n --brand-gradient-soft: linear-gradient(135deg, rgba(8,145,178,0.10), rgba(124,58,237,0.10));\n --success: #059669;\n --warning: #d97706;\n --danger: #dc2626;\n --info: #2563eb;\n --shadow-sm: 0 1px 2px rgba(15,23,42,0.06);\n --shadow: 0 6px 22px -6px rgba(15,23,42,0.14), 0 0 0 1px var(--border);\n --shadow-lg: 0 18px 50px -12px rgba(15,23,42,0.2), 0 0 0 1px var(--border-strong);\n --shadow-glow: 0 0 32px rgba(8,145,178,0.12);\n --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.5), rgba(255,255,255,0) 42%);\n }\n html[data-theme=\"light\"] body::before {\n background:\n radial-gradient(ellipse at 10% 0%, rgba(124,58,237,0.06) 0%, transparent 45%),\n radial-gradient(ellipse at 90% 100%, rgba(8,145,178,0.05) 0%, transparent 50%);\n }\n html[data-theme=\"light\"] ::-webkit-scrollbar-thumb { background: rgba(15,23,42,0.15); }\n html[data-theme=\"light\"] ::-webkit-scrollbar-thumb:hover { background: rgba(124,58,237,0.35); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Reset + base */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html, body {\n height: 100%;\n font-family: var(--font-sans);\n font-size: 14px;\n background: var(--bg);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n line-height: 1.5;\n overflow: hidden;\n }\n body { display: flex; flex-direction: column; }\n a { color: var(--brand-cyan); text-decoration: none; }\n a:hover { text-decoration: underline; }\n button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }\n input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }\n\n /* Ambient backdrop */\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n pointer-events: none;\n background:\n radial-gradient(ellipse at 10% 0%, rgba(168,85,247,0.10) 0%, transparent 45%),\n radial-gradient(ellipse at 90% 100%, rgba(24,255,255,0.08) 0%, transparent 50%),\n radial-gradient(ellipse at 50% 50%, rgba(236,72,153,0.04) 0%, transparent 60%);\n z-index: 0;\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Scrollbars */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n ::-webkit-scrollbar { width: 8px; height: 8px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }\n ::-webkit-scrollbar-thumb:hover { background: rgba(168,85,247,0.4); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Header */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n header {\n height: var(--header-h);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px) saturate(180%);\n -webkit-backdrop-filter: blur(24px) saturate(180%);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 0 20px;\n position: relative;\n z-index: 100;\n flex-shrink: 0;\n }\n .brain-hero {\n position: relative;\n width: 44px; height: 44px;\n flex-shrink: 0;\n }\n .brain-hero .aura {\n position: absolute; inset: -8px;\n border-radius: 50%;\n background: radial-gradient(circle, rgba(168,85,247,0.55) 0%, rgba(24,255,255,0.15) 40%, transparent 75%);\n animation: auraPulse 3.2s ease-in-out infinite;\n filter: blur(6px);\n }\n @keyframes auraPulse { 50% { transform: scale(1.22); opacity: 1; } }\n .brain-hero svg { position: relative; width: 100%; height: 100%; filter: drop-shadow(0 0 8px rgba(24,255,255,0.4)); }\n .brain-hero .hemi { fill: none; stroke: var(--brand-cyan); stroke-width: 1.4; stroke-linecap: round; opacity: 0.9; }\n .brain-hero .hemi-r { stroke: var(--brand-purple); }\n .brain-hero .synapse { animation: synapse 1.4s ease-in-out infinite; transform-origin: center; transform-box: fill-box; }\n @keyframes synapse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }\n .brain-hero .charge { stroke-dasharray: 2 6; animation: rot 2s linear infinite; }\n @keyframes rot { to { stroke-dashoffset: -20; } }\n\n .brand {\n display: flex; flex-direction: column; line-height: 1.15;\n }\n .brand .name {\n font-size: 15px; font-weight: 800; letter-spacing: -0.2px;\n background: var(--brand-gradient);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n }\n .brand .state {\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--brand-cyan);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n display: flex; align-items: center; gap: 5px;\n }\n .brand .state::before {\n content: \"\"; width: 5px; height: 5px; border-radius: 50%;\n background: var(--success); box-shadow: 0 0 6px var(--success);\n animation: pulse 1.5s infinite;\n }\n @keyframes pulse { 50% { opacity: 0.5; } }\n\n .header-stats {\n display: flex; gap: 8px; margin-left: 16px;\n flex-wrap: nowrap; overflow-x: auto;\n scrollbar-width: none;\n }\n .header-stats::-webkit-scrollbar { display: none; }\n .stat-pill {\n display: flex; align-items: center; gap: 6px;\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 999px;\n padding: 5px 12px;\n font-size: 11px;\n color: var(--text-2);\n white-space: nowrap;\n transition: all var(--duration) var(--easing);\n }\n .stat-pill:hover { border-color: var(--border-strong); color: var(--text); }\n .stat-pill .n { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }\n\n .header-right {\n margin-left: auto;\n display: flex; gap: 8px; align-items: center;\n }\n .icon-btn {\n width: 34px; height: 34px;\n display: flex; align-items: center; justify-content: center;\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 10px;\n color: var(--text-2);\n transition: all var(--duration) var(--easing);\n }\n .icon-btn:hover { background: var(--bg-elev-3); color: var(--text); border-color: var(--border-strong); }\n .icon-btn svg { width: 16px; height: 16px; }\n .kbd-hint {\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 5px 10px;\n font-family: var(--font-mono); font-size: 10px;\n color: var(--text-3);\n display: flex; align-items: center; gap: 6px;\n transition: all var(--duration);\n }\n .kbd-hint:hover { color: var(--text); border-color: var(--brand-cyan); }\n kbd {\n background: var(--bg-elev-3);\n border-radius: 4px;\n padding: 1px 5px;\n font-size: 9px;\n font-family: var(--font-mono);\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Layout: sidebar + main */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .layout {\n display: grid;\n grid-template-columns: var(--sidebar-w) 1fr;\n flex: 1;\n overflow: hidden;\n position: relative;\n z-index: 1;\n }\n\n aside.sidebar {\n background: var(--bg-glass);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 16px 10px;\n }\n .nav-group { margin-bottom: 14px; }\n .nav-group-label {\n font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;\n color: var(--text-3); padding: 6px 12px; font-weight: 700;\n }\n /* Simple / Advanced mode toggle */\n .mode-toggle {\n display: flex; gap: 3px; padding: 3px;\n background: var(--bg-elev-1); border: 1px solid var(--border);\n border-radius: 999px; margin: 0 6px 14px;\n }\n .mode-btn {\n flex: 1; padding: 6px 10px; border-radius: 999px;\n font-size: 11.5px; font-weight: 700; color: var(--text-3);\n transition: all var(--duration-fast) var(--easing);\n }\n .mode-btn:hover { color: var(--text-2); }\n .mode-btn.active { background: var(--brand-gradient); color: #fff; box-shadow: 0 2px 10px -2px rgba(168,85,247,0.5); }\n /* In simple mode, advanced groups are hidden entirely. */\n html[data-navmode=\"simple\"] [data-adv] { display: none; }\n .nav-item {\n display: flex; align-items: center; gap: 10px;\n padding: 8px 12px;\n border-radius: 9px;\n cursor: pointer;\n font-size: 12.5px;\n color: var(--text-2);\n transition: background var(--duration-fast) var(--easing), color var(--duration-fast) var(--easing), transform var(--duration-fast) var(--easing);\n position: relative;\n width: 100%; text-align: left;\n font-weight: 500;\n }\n .nav-item:hover { background: var(--bg-elev-2); color: var(--text); transform: translateX(2px); }\n .nav-item.active {\n background: var(--brand-gradient-soft);\n color: var(--text);\n font-weight: 600;\n }\n .nav-item.active::before {\n content: \"\";\n position: absolute; left: 0; top: 5px; bottom: 5px;\n width: 3px; border-radius: 0 3px 3px 0;\n background: var(--brand-gradient);\n box-shadow: 0 0 10px var(--brand-cyan);\n }\n .nav-item svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-3); transition: color var(--duration-fast); }\n .nav-item:hover svg, .nav-item.active svg { color: var(--brand-cyan); }\n .nav-item .badge-count {\n margin-left: auto;\n background: var(--bg-elev-3);\n border-radius: 9px;\n padding: 0px 7px;\n font-size: 10px;\n color: var(--text-3);\n font-variant-numeric: tabular-nums;\n }\n .nav-item.active .badge-count { background: var(--brand-cyan); color: #000; }\n\n main {\n overflow-y: auto;\n padding: 20px 26px 48px;\n position: relative;\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Sections */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .section { display: none; }\n .section.active { display: block; animation: sectionIn var(--duration-slow) var(--easing); }\n @keyframes sectionIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }\n\n .section-header {\n display: flex; align-items: flex-end; justify-content: space-between;\n margin-bottom: 18px; flex-wrap: wrap; gap: 12px;\n }\n .section-title {\n font-size: 22px; font-weight: 800; letter-spacing: -0.5px;\n display: flex; align-items: center; gap: 10px;\n }\n .section-title svg { color: var(--brand-cyan); width: 22px; height: 22px; }\n .section-subtitle {\n color: var(--text-3); font-size: 12.5px; margin-top: 4px;\n }\n .section-actions { display: flex; gap: 8px; flex-wrap: wrap; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Cards */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 14px;\n }\n .grid.cols-2 { grid-template-columns: repeat(2, 1fr); }\n .grid.cols-3 { grid-template-columns: repeat(3, 1fr); }\n .grid.cols-4 { grid-template-columns: repeat(4, 1fr); }\n\n .card {\n background: var(--bg-glass);\n backdrop-filter: blur(20px) saturate(150%);\n -webkit-backdrop-filter: blur(20px) saturate(150%);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 18px;\n transition: transform var(--duration) var(--easing), box-shadow var(--duration) var(--easing), border-color var(--duration) var(--easing);\n position: relative;\n overflow: hidden;\n box-shadow: var(--shadow-sm);\n }\n .card::after {\n content: \"\";\n position: absolute; inset: 0 0 auto 0; height: 50%;\n background: var(--card-sheen);\n pointer-events: none; border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n }\n .card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow); }\n .card.span-2 { grid-column: span 2; }\n .card.span-full { grid-column: 1 / -1; }\n .card.accent::before {\n content: \"\";\n position: absolute; inset: 0; pointer-events: none;\n background: linear-gradient(135deg, rgba(24,255,255,0.08), transparent 50%);\n }\n /* Canvas-bearing cards (constellation, charts, graphs) skip the top sheen so\n visualizations stay crisp \u2014 especially in light theme. */\n .card:has(canvas)::after { display: none; }\n .card-header {\n display: flex; align-items: center; justify-content: space-between;\n margin-bottom: 14px; gap: 8px;\n }\n .card-title {\n font-size: 13px; font-weight: 700; letter-spacing: 0.01em;\n display: flex; align-items: center; gap: 8px;\n }\n .card-title svg { width: 14px; height: 14px; color: var(--brand-cyan); }\n\n .stat-card { padding: 18px; }\n .stat-card .label { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }\n .stat-card .value { font-size: 32px; font-weight: 900; letter-spacing: -1px; margin: 6px 0 4px; font-variant-numeric: tabular-nums; }\n .stat-card .value.accent { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }\n .stat-card .trend { font-size: 11px; color: var(--text-2); display: flex; align-items: center; gap: 4px; }\n .stat-card .trend.up { color: var(--success); }\n .stat-card .trend.down { color: var(--danger); }\n\n .workflow-strip {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 12px;\n align-items: end;\n margin-bottom: 16px;\n }\n .workflow-path {\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text-2);\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 10px 12px;\n width: 100%;\n }\n .warning-stack { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }\n .warning-banner {\n display: flex;\n gap: 10px;\n align-items: flex-start;\n border: 1px solid rgba(217, 119, 6, 0.38);\n background: rgba(217, 119, 6, 0.10);\n color: var(--warning);\n border-radius: 8px;\n padding: 10px 12px;\n font-size: 12px;\n }\n .brain-feed { display: flex; flex-direction: column; gap: 10px; }\n .stack { display: flex; flex-direction: column; gap: 8px; }\n .memory-item {\n border: 1px solid var(--border);\n background: var(--bg-elev-1);\n border-radius: 8px;\n padding: 10px 12px;\n }\n .memory-item .meta {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n align-items: center;\n margin-bottom: 6px;\n font-size: 11px;\n color: var(--text-3);\n }\n .memory-item .content {\n color: var(--text-2);\n font-size: 12px;\n line-height: 1.45;\n word-break: break-word;\n }\n .ops-agent-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 8px;\n }\n .ops-agent {\n border: 1px solid var(--border);\n background: var(--bg-elev-1);\n border-radius: 8px;\n padding: 9px 10px;\n font-size: 12px;\n }\n .ops-agent .name { font-weight: 700; margin-bottom: 5px; }\n .ops-agent .status { display: flex; gap: 6px; flex-wrap: wrap; }\n .empty-tight { color: var(--text-3); padding: 18px 4px; font-size: 12px; text-align: center; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Rows, pills, badges */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .row {\n display: flex; justify-content: space-between; align-items: center;\n padding: 9px 6px; border-bottom: 1px solid var(--border);\n gap: 8px; border-radius: 6px;\n transition: background var(--duration-fast) var(--easing);\n }\n .row:hover { background: var(--bg-elev-2); }\n .row:last-child { border: none; }\n .row .k { font-size: 12px; color: var(--text-2); }\n .row .v { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }\n .row .v.good { color: var(--success); }\n .row .v.warn { color: var(--warning); }\n .row .v.bad { color: var(--danger); }\n\n .pill {\n display: inline-flex; align-items: center; gap: 4px;\n padding: 3px 9px; border-radius: 999px;\n background: var(--bg-elev-3); border: 1px solid var(--border);\n font-size: 10px; font-weight: 700; letter-spacing: 0.02em;\n color: var(--text-2);\n }\n .pill.green { background: rgba(16,230,140,.12); color: var(--success); border-color: rgba(16,230,140,.3); }\n .pill.red { background: rgba(255,88,100,.12); color: var(--danger); border-color: rgba(255,88,100,.3); }\n .pill.yellow { background: rgba(255,181,63,.12); color: var(--warning); border-color: rgba(255,181,63,.3); }\n .pill.purple { background: rgba(168,85,247,.15); color: var(--brand-purple); border-color: rgba(168,85,247,.3); }\n .pill.cyan { background: rgba(24,255,255,.12); color: var(--brand-cyan); border-color: rgba(24,255,255,.3); }\n .pill.pink { background: rgba(236,72,153,.12); color: var(--brand-pink); border-color: rgba(236,72,153,.3); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Buttons */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .btn {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 8px 14px;\n background: var(--bg-elev-2);\n border: 1px solid var(--border);\n border-radius: 9px;\n color: var(--text);\n font-size: 12.5px;\n font-weight: 600;\n transition: transform var(--duration-fast) var(--easing), box-shadow var(--duration) var(--easing), background var(--duration) var(--easing), border-color var(--duration) var(--easing), filter var(--duration) var(--easing);\n white-space: nowrap;\n box-shadow: var(--shadow-sm);\n }\n .btn:hover { background: var(--bg-elev-3); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow); }\n .btn:active { transform: translateY(0) scale(0.98); }\n .btn svg { width: 13px; height: 13px; }\n .btn.sm { padding: 4px 10px; font-size: 11px; border-radius: 7px; }\n .btn.lg { padding: 11px 18px; font-size: 13px; border-radius: 11px; }\n .btn.primary {\n background: var(--brand-gradient);\n color: #fff;\n border-color: transparent;\n box-shadow: 0 4px 14px -4px rgba(168,85,247,0.5);\n }\n .btn.primary:hover { filter: brightness(1.08); transform: translateY(-1px); box-shadow: 0 8px 24px -4px rgba(24,255,255,0.4); }\n .btn.ghost { background: transparent; }\n .btn.ghost:hover { background: var(--bg-elev-2); }\n .btn.danger { color: var(--danger); border-color: rgba(255,88,100,.35); }\n .btn.danger:hover { background: rgba(255,88,100,.1); }\n .btn.success { color: var(--success); border-color: rgba(16,230,140,.35); }\n .btn.success:hover { background: rgba(16,230,140,.1); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Forms */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .field { margin-bottom: 12px; }\n .field label {\n display: block; margin-bottom: 5px;\n font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--text-3);\n }\n .field input, .field textarea, .field select {\n width: 100%;\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 9px 12px;\n font-size: 12.5px;\n color: var(--text);\n transition: all var(--duration) var(--easing);\n }\n .field input:focus, .field textarea:focus, .field select:focus {\n outline: none;\n border-color: var(--brand-cyan);\n box-shadow: 0 0 0 3px rgba(24,255,255,0.12);\n }\n .field textarea { resize: vertical; min-height: 70px; font-family: var(--font-mono); font-size: 12px; }\n .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\n\n /* Toggle switch */\n .switch { position: relative; display: inline-block; width: 38px; height: 22px; }\n .switch input { opacity: 0; width: 0; height: 0; }\n .switch .slider {\n position: absolute; inset: 0; cursor: pointer;\n background: var(--bg-elev-3);\n border: 1px solid var(--border);\n transition: var(--duration); border-radius: 12px;\n }\n .switch .slider::before {\n content: \"\"; position: absolute;\n height: 16px; width: 16px; left: 2px; top: 2px;\n background: var(--text-2); transition: var(--duration); border-radius: 50%;\n }\n .switch input:checked + .slider { background: var(--brand-gradient); border-color: transparent; }\n .switch input:checked + .slider::before { transform: translateX(16px); background: #fff; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Tables */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .table { width: 100%; border-collapse: collapse; }\n .table th, .table td { padding: 11px 14px; text-align: left; font-size: 12px; }\n .table th {\n background: var(--bg-elev-2);\n color: var(--text-3);\n font-weight: 700; letter-spacing: 0.06em;\n text-transform: uppercase; font-size: 10px;\n border-bottom: 1px solid var(--border-strong);\n position: sticky; top: 0; z-index: 1;\n }\n .table tbody tr { border-bottom: 1px solid var(--border); transition: background var(--duration-fast), box-shadow var(--duration-fast); }\n .table tbody tr:last-child { border-bottom: none; }\n .table tbody tr:hover { background: var(--brand-gradient-soft); box-shadow: inset 3px 0 0 var(--brand-cyan); }\n .table code { color: var(--brand-cyan); font-size: 11px; }\n\n .empty {\n padding: 32px 16px; text-align: center;\n color: var(--text-3); font-size: 12px;\n }\n .empty svg { width: 40px; height: 40px; color: var(--text-dim); margin: 0 auto 10px; display: block; }\n\n /* Progress meters */\n .meter { height: 6px; background: var(--bg-elev-3); border-radius: 4px; overflow: hidden; position: relative; }\n .meter .fill {\n height: 100%; border-radius: 4px;\n background: var(--brand-gradient);\n transition: width var(--duration-slow) var(--easing);\n }\n .meter .fill.good { background: var(--success); }\n .meter .fill.warn { background: var(--warning); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Feature toggles grid */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .feature-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }\n .feature-toggle {\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 12px 14px;\n display: flex; align-items: center; gap: 12px;\n transition: all var(--duration) var(--easing);\n }\n .feature-toggle:hover { border-color: var(--border-strong); }\n .feature-toggle .icon {\n width: 32px; height: 32px;\n background: var(--brand-gradient-soft);\n border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n color: var(--brand-cyan); flex-shrink: 0;\n }\n .feature-toggle .icon svg { width: 16px; height: 16px; }\n .feature-toggle .meta { flex: 1; min-width: 0; }\n .feature-toggle .meta .name { font-size: 12.5px; font-weight: 600; }\n .feature-toggle .meta .desc { font-size: 10.5px; color: var(--text-3); margin-top: 2px; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Command palette */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .cmd-overlay {\n position: fixed; inset: 0;\n background: rgba(3, 5, 9, 0.72);\n backdrop-filter: blur(8px);\n z-index: 2000;\n display: none; align-items: flex-start; justify-content: center;\n padding-top: 12vh;\n animation: fadeIn var(--duration) var(--easing);\n }\n .cmd-overlay.open { display: flex; }\n @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n .cmd-palette {\n width: min(640px, 92vw);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(36px);\n border: 1px solid var(--border-strong);\n border-radius: 14px;\n overflow: hidden;\n box-shadow: var(--shadow-lg);\n animation: paletteIn var(--duration-slow) var(--easing-spring);\n }\n @keyframes paletteIn { from { opacity: 0; transform: translateY(-20px) scale(0.96); } to { opacity: 1; transform: none; } }\n\n .cmd-palette input {\n width: 100%;\n background: transparent; border: none; outline: none;\n padding: 16px 20px;\n font-size: 14px; color: var(--text);\n border-bottom: 1px solid var(--border);\n }\n .cmd-palette .results { max-height: 420px; overflow-y: auto; padding: 6px; }\n .cmd-result {\n display: flex; align-items: center; gap: 10px;\n padding: 9px 12px; border-radius: 7px;\n cursor: pointer; color: var(--text-2);\n transition: all var(--duration-fast);\n }\n .cmd-result:hover, .cmd-result.selected { background: var(--bg-elev-2); color: var(--text); }\n .cmd-result svg { width: 15px; height: 15px; color: var(--brand-cyan); flex-shrink: 0; }\n .cmd-result .meta { flex: 1; font-size: 12.5px; }\n .cmd-result .hint { font-size: 10px; color: var(--text-3); font-family: var(--font-mono); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Toasts */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .toast-container {\n position: fixed; bottom: 52px; right: 20px;\n display: flex; flex-direction: column; gap: 8px;\n z-index: 1500; pointer-events: none;\n }\n .toast {\n min-width: 260px; max-width: 360px;\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px);\n border: 1px solid var(--border-strong);\n border-left: 3px solid var(--brand-cyan);\n border-radius: 10px;\n padding: 10px 14px;\n pointer-events: auto;\n display: flex; align-items: flex-start; gap: 10px;\n animation: toastIn var(--duration-slow) var(--easing-spring);\n box-shadow: var(--shadow-lg);\n }\n .toast.success { border-left-color: var(--success); }\n .toast.warn { border-left-color: var(--warning); }\n .toast.error { border-left-color: var(--danger); }\n .toast.removing { animation: toastOut var(--duration-slow) var(--easing) forwards; }\n @keyframes toastIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: none; } }\n @keyframes toastOut { to { opacity: 0; transform: translateX(20px); } }\n .toast svg { width: 15px; height: 15px; color: var(--brand-cyan); flex-shrink: 0; margin-top: 1px; }\n .toast.success svg { color: var(--success); }\n .toast.warn svg { color: var(--warning); }\n .toast.error svg { color: var(--danger); }\n .toast .title { font-size: 12.5px; font-weight: 600; margin-bottom: 2px; }\n .toast .body { font-size: 11.5px; color: var(--text-2); line-height: 1.4; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Ticker + log panel */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .log-ticker {\n position: fixed; bottom: 0; left: 0; right: 0;\n height: var(--ticker-h);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px);\n border-top: 1px solid var(--border);\n display: flex; align-items: center; gap: 12px;\n padding: 0 20px;\n font-family: var(--font-mono); font-size: 10.5px;\n color: var(--text-3);\n z-index: 600;\n }\n .log-ticker .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); animation: pulse 1.5s infinite; flex-shrink: 0; }\n .log-ticker .scroll { flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n .log-panel {\n position: fixed; bottom: var(--ticker-h); left: 0; right: 0;\n height: 300px;\n background: var(--bg-elev-1);\n border-top: 1px solid var(--border);\n padding: 16px 20px;\n overflow-y: auto;\n font-family: var(--font-mono); font-size: 11.5px;\n color: var(--text-2);\n z-index: 599;\n transform: translateY(100%);\n transition: transform var(--duration-slow) var(--easing);\n box-shadow: 0 -8px 32px rgba(0,0,0,0.3);\n }\n .log-panel.open { transform: translateY(0); }\n .log-panel .head {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: 10px;\n }\n .log-panel .head .title { color: var(--brand-cyan); font-weight: 700; letter-spacing: 0.04em; }\n .log-row {\n padding: 4px 0;\n border-bottom: 1px solid var(--border);\n display: grid; grid-template-columns: 90px 130px 1fr; gap: 10px; align-items: baseline;\n }\n .log-row .t { color: var(--text-dim); font-size: 10px; }\n .log-row .src { color: var(--brand-purple); font-weight: 600; font-size: 10.5px; }\n .log-row .type { color: var(--brand-cyan); font-weight: 600; font-size: 10.5px; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Responsive */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .menu-toggle { display: none; }\n\n @media (max-width: 1024px) {\n .grid.cols-3, .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }\n .card.span-2 { grid-column: 1 / -1; }\n }\n\n @media (max-width: 768px) {\n header { padding: 0 14px; gap: 10px; }\n .header-stats { display: none; }\n .brand .state { display: none; }\n .menu-toggle { display: flex; }\n aside.sidebar {\n position: fixed;\n top: var(--header-h); bottom: 0;\n width: 280px; z-index: 90;\n transform: translateX(-100%);\n transition: transform var(--duration-slow) var(--easing);\n }\n aside.sidebar.open { transform: translateX(0); box-shadow: var(--shadow-lg); }\n .layout { grid-template-columns: 1fr; }\n main { padding: 14px; padding-bottom: 60px; }\n .grid, .grid.cols-2, .grid.cols-3, .grid.cols-4 { grid-template-columns: 1fr; }\n .card.span-2 { grid-column: auto; }\n .section-title { font-size: 18px; }\n .field-row { grid-template-columns: 1fr; }\n }\n\n /* Chart canvas wrapper */\n .chart-wrap { position: relative; height: 220px; }\n .chart-wrap.lg { height: 320px; }\n\n /* Live Graph canvas */\n .graph-wrap {\n position: relative;\n background: radial-gradient(ellipse at center, rgba(168,85,247,0.04) 0%, transparent 70%);\n border-radius: 12px; overflow: hidden;\n border: 1px solid var(--border);\n }\n .graph-wrap canvas { display: block; width: 100%; height: 560px; }\n\n /* Helpers */\n .mono { font-family: var(--font-mono); font-size: 11.5px; }\n .tag-code { font-family: var(--font-mono); background: var(--bg-elev-2); padding: 1px 6px; border-radius: 4px; font-size: 11px; }\n .flex { display: flex; }\n .flex.center { align-items: center; }\n .flex.gap { gap: 8px; }\n .flex.between { justify-content: space-between; }\n .grow { flex: 1; }\n .hidden { display: none; }\n .mt { margin-top: 10px; } .mt-lg { margin-top: 20px; }\n .mb { margin-bottom: 10px; } .mb-lg { margin-bottom: 20px; }\n\n /* Accent bar */\n .accent-bar {\n height: 3px; border-radius: 2px;\n background: var(--brand-gradient);\n margin-bottom: 18px;\n box-shadow: 0 0 12px rgba(24,255,255,0.35);\n }\n\n /* Skeleton loader */\n .skel { background: linear-gradient(90deg, var(--bg-elev-2) 0%, var(--bg-elev-3) 50%, var(--bg-elev-2) 100%); background-size: 200% 100%; animation: skel 1.2s linear infinite; border-radius: 6px; }\n @keyframes skel { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Realtime activity (v6.1) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .live-badge {\n display: inline-flex; align-items: center; gap: 5px;\n font-family: var(--font-mono); font-size: 9px; font-weight: 800; letter-spacing: 0.12em;\n color: var(--success); background: rgba(16,230,140,.12);\n border: 1px solid rgba(16,230,140,.32); border-radius: 999px;\n padding: 2px 8px; vertical-align: middle;\n }\n .live-badge::before {\n content: \"\"; width: 6px; height: 6px; border-radius: 50%;\n background: var(--success); box-shadow: 0 0 6px var(--success);\n animation: pulse 1.3s infinite;\n }\n .live-badge.paused { color: var(--warning); background: rgba(255,181,63,.12); border-color: rgba(255,181,63,.32); }\n .live-badge.paused::before { background: var(--warning); box-shadow: none; animation: none; }\n .filter-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }\n .chip {\n padding: 5px 12px; border-radius: 999px; font-size: 11.5px; font-weight: 600;\n background: var(--bg-elev-2); border: 1px solid var(--border); color: var(--text-2);\n transition: all var(--duration-fast) var(--easing);\n }\n .chip:hover { border-color: var(--border-strong); color: var(--text); }\n .chip.active { background: var(--brand-gradient); color: #fff; border-color: transparent; }\n .act-search {\n background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 8px;\n padding: 7px 12px; color: var(--text); font-size: 12px; min-width: 180px;\n }\n .act-search:focus { outline: none; border-color: var(--brand-cyan); box-shadow: 0 0 0 3px rgba(24,255,255,0.12); }\n tr.act-fresh { animation: actFreshIn var(--duration-slow) var(--easing); }\n @keyframes actFreshIn { from { background: rgba(24,255,255,0.12); opacity: 0; transform: translateY(-4px); } to { background: transparent; opacity: 1; transform: none; } }\n .conn-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); box-shadow: 0 0 6px var(--success); transition: all var(--duration); }\n .conn-dot.offline { background: var(--warning); box-shadow: none; animation: pulse 1s infinite; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Memory Constellation (v6.2) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .const-wrap {\n position: relative;\n height: 600px;\n background:\n radial-gradient(ellipse at 30% 20%, rgba(24,255,255,0.05) 0%, transparent 55%),\n radial-gradient(ellipse at 75% 80%, rgba(168,85,247,0.06) 0%, transparent 55%);\n overflow: hidden;\n cursor: grab;\n }\n .const-wrap.dragging { cursor: grabbing; }\n .const-wrap canvas { display: block; width: 100%; height: 100%; }\n .const-legend {\n position: absolute; top: 12px; left: 12px;\n display: flex; flex-direction: column; gap: 5px;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border); border-radius: 8px; padding: 8px 11px;\n font-size: 11px; color: var(--text-2); pointer-events: none;\n }\n .const-legend .lg-row { display: flex; align-items: center; gap: 7px; }\n .const-legend .lg-dot { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 6px currentColor; }\n .const-meta {\n position: absolute; top: 12px; right: 12px;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border); border-radius: 8px; padding: 6px 11px;\n font-family: var(--font-mono); font-size: 10.5px; color: var(--brand-cyan); pointer-events: none;\n }\n .const-hint {\n position: absolute; bottom: 10px; left: 12px;\n font-size: 10.5px; color: var(--text-3); pointer-events: none;\n background: var(--bg-glass); border-radius: 6px; padding: 3px 9px;\n }\n .const-empty {\n position: absolute; inset: 0; display: none;\n flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 8px; padding: 24px;\n }\n .const-empty.show { display: flex; }\n .const-empty svg { width: 52px; height: 52px; color: var(--text-dim); margin-bottom: 4px; }\n .const-empty .ce-title { font-size: 15px; font-weight: 700; color: var(--text-2); }\n .const-empty .ce-msg { font-size: 12px; color: var(--text-3); max-width: 360px; line-height: 1.5; }\n .const-drawer {\n position: absolute; top: 0; right: 0; bottom: 0; width: min(340px, 80%);\n background: var(--bg-glass-strong); backdrop-filter: blur(28px);\n border-left: 1px solid var(--border-strong);\n transform: translateX(100%); transition: transform var(--duration-slow) var(--easing);\n padding: 16px; overflow-y: auto; z-index: 5;\n }\n .const-drawer.open { transform: translateX(0); box-shadow: -12px 0 40px rgba(0,0,0,0.35); }\n .const-drawer-close {\n position: absolute; top: 10px; right: 12px;\n width: 26px; height: 26px; border-radius: 6px; font-size: 18px; line-height: 1;\n color: var(--text-2); background: var(--bg-elev-2); border: 1px solid var(--border);\n }\n .const-drawer-close:hover { color: var(--text); background: var(--bg-elev-3); }\n .cd-type { font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }\n .cd-title { font-size: 15px; font-weight: 700; margin: 6px 0 12px; line-height: 1.35; word-break: break-word; }\n .cd-section { margin-top: 12px; }\n .cd-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-3); margin-bottom: 4px; font-weight: 700; }\n .cd-content { font-size: 12px; color: var(--text-2); line-height: 1.5; word-break: break-word; }\n .const-tip {\n position: absolute; pointer-events: none; z-index: 6; display: none;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border-strong); border-radius: 7px; padding: 6px 10px;\n font-size: 11px; color: var(--text); max-width: 260px;\n transform: translate(-50%, calc(-100% - 12px));\n }\n .const-tip.show { display: block; }\n .tag-cloud { display: flex; flex-wrap: wrap; gap: 7px; }\n .tag-cloud .tc {\n border: 1px solid var(--border); border-radius: 999px; padding: 4px 11px;\n font-size: 12px; color: var(--text-2); background: var(--bg-elev-1);\n }\n .tag-cloud .tc b { color: var(--brand-cyan); font-variant-numeric: tabular-nums; }\n\n /* Overview constellation hero */\n .const-hero { background: linear-gradient(120deg, rgba(24,255,255,0.08), rgba(168,85,247,0.08) 60%, transparent); }\n .const-hero:hover { border-color: var(--border-accent); box-shadow: var(--shadow-glow); }\n .const-hero-inner { display: flex; align-items: center; gap: 20px; }\n .const-hero-art { flex-shrink: 0; width: 132px; height: 96px; }\n .const-hero-art svg { width: 100%; height: 100%; filter: drop-shadow(0 0 8px rgba(24,255,255,0.35)); }\n .const-hero-art .he { stroke: rgba(24,255,255,0.4); stroke-width: 1.2; }\n .const-hero-art circle { animation: synapse 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }\n .const-hero-body { flex: 1; min-width: 0; }\n .const-hero-title { font-size: 19px; font-weight: 800; letter-spacing: -0.4px; background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }\n .const-hero-sub { font-size: 12.5px; color: var(--text-3); margin: 4px 0 10px; }\n .const-hero-stats { display: flex; gap: 18px; font-size: 12px; color: var(--text-2); }\n .const-hero-stats b { font-size: 17px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; margin-right: 4px; }\n @media (max-width: 768px) { .const-hero-art { display: none; } .const-hero-inner { gap: 12px; } }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Revived tab visualizations (v6.2) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Reusable empty-state */\n .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: 36px 20px; }\n .empty-state svg { width: 44px; height: 44px; color: var(--text-dim); margin-bottom: 4px; }\n .empty-state .es-title { font-size: 14px; font-weight: 700; color: var(--text-2); }\n .empty-state .es-msg { font-size: 12px; color: var(--text-3); max-width: 380px; line-height: 1.5; }\n .empty-state .es-cta { margin-top: 6px; }\n\n /* Risk gauge + matrix (Pre-Mortem) */\n .risk-head { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; }\n .risk-gauge { position: relative; width: 96px; height: 96px; flex-shrink: 0; }\n .risk-gauge canvas { width: 96px; height: 96px; }\n .risk-gauge .rg-num { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 900; }\n .risk-matrix { display: grid; grid-template-columns: 64px repeat(4, 1fr); gap: 5px; margin: 14px 0; }\n .risk-matrix .rm-h { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3); display: flex; align-items: center; justify-content: center; padding: 4px; }\n .risk-matrix .rm-yl { font-size: 10px; color: var(--text-3); display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; }\n .risk-matrix .rm-cell { aspect-ratio: 2.6/1; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px; color: #fff; min-height: 30px; transition: all var(--duration); }\n .risk-matrix .rm-cell.empty { background: var(--bg-elev-1); color: var(--text-dim); font-weight: 400; }\n .fail-card { padding: 12px 14px; background: var(--bg-elev-1); border: 1px solid var(--border); border-left-width: 3px; border-radius: 8px; margin-bottom: 8px; }\n .fail-card .fc-head { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }\n .fail-card .fc-desc { font-size: 12.5px; color: var(--text); line-height: 1.45; }\n .fail-card .fc-mit { font-size: 11.5px; color: var(--text-2); margin-top: 6px; padding-left: 14px; border-left: 2px solid var(--success); }\n\n /* Attention heat bars */\n .heat-row { margin-bottom: 10px; }\n .heat-row .hr-top { display: flex; justify-content: space-between; align-items: center; font-size: 11.5px; margin-bottom: 4px; gap: 8px; }\n .heat-row .hr-pct { font-family: var(--font-mono); font-weight: 800; flex-shrink: 0; }\n .heat-row .hr-track { height: 22px; background: var(--bg-elev-1); border-radius: 5px; overflow: hidden; border: 1px solid var(--border); }\n .heat-row .hr-fill { height: 100%; border-radius: 5px 0 0 5px; transition: width var(--duration-slow) var(--easing); }\n .heat-row .hr-reason { font-size: 10.5px; color: var(--text-3); margin-top: 3px; }\n\n /* Reputation trust cards */\n .trust-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }\n .trust-card { background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; transition: all var(--duration); }\n .trust-card:hover { border-color: var(--border-accent); transform: translateY(-2px); }\n .trust-ring { position: relative; width: 92px; height: 92px; }\n .trust-ring canvas { width: 92px; height: 92px; }\n .trust-ring .tr-pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 19px; font-weight: 900; }\n .trust-card .tc-name { font-weight: 700; font-size: 13px; }\n .trust-card .tc-sub { font-size: 11px; color: var(--text-3); }\n\n /* Causal / DAG canvas */\n .dag-wrap { position: relative; height: 420px; background: radial-gradient(ellipse at center, rgba(168,85,247,0.04), transparent 70%); border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }\n .dag-wrap canvas { display: block; width: 100%; height: 100%; }\n .dag-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--text-3); font-size: 12.5px; text-align: center; padding: 20px; }\n</style>\n</head>\n<body>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Header\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<header>\n <button class=\"icon-btn menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Menu\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"/></svg>\n </button>\n\n <div class=\"brain-hero\" title=\"Hive Mind \u2014 learning \u00B7 reflecting \u00B7 charging\">\n <div class=\"aura\"></div>\n <svg viewBox=\"0 0 100 100\">\n <circle class=\"hemi charge\" cx=\"50\" cy=\"50\" r=\"46\"/>\n <circle class=\"hemi charge\" cx=\"50\" cy=\"50\" r=\"40\" style=\"animation-duration:3s;animation-direction:reverse\"/>\n <path class=\"hemi\" d=\"M 48 22 C 30 22, 18 32, 18 48 C 18 60, 26 72, 38 78 C 42 82, 46 80, 48 76 L 48 22\"/>\n <path class=\"hemi hemi-r\" d=\"M 52 22 C 70 22, 82 32, 82 48 C 82 60, 74 72, 62 78 C 58 82, 54 80, 52 76 L 52 22\"/>\n <path class=\"hemi\" d=\"M 30 40 Q 38 38, 44 44\"/>\n <path class=\"hemi\" d=\"M 28 55 Q 36 55, 44 58\"/>\n <path class=\"hemi\" d=\"M 32 68 Q 40 66, 46 72\"/>\n <path class=\"hemi hemi-r\" d=\"M 70 40 Q 62 38, 56 44\"/>\n <path class=\"hemi hemi-r\" d=\"M 72 55 Q 64 55, 56 58\"/>\n <path class=\"hemi hemi-r\" d=\"M 68 68 Q 60 66, 54 72\"/>\n <circle class=\"synapse\" cx=\"30\" cy=\"42\" r=\"1.8\" fill=\"#18ffff\"/>\n <circle class=\"synapse\" cx=\"70\" cy=\"42\" r=\"1.8\" fill=\"#a855f7\" style=\"animation-delay:0.3s\"/>\n <circle class=\"synapse\" cx=\"35\" cy=\"62\" r=\"1.6\" fill=\"#10e68c\" style=\"animation-delay:0.6s\"/>\n <circle class=\"synapse\" cx=\"65\" cy=\"62\" r=\"1.6\" fill=\"#ffb53f\" style=\"animation-delay:0.9s\"/>\n <circle class=\"synapse\" cx=\"50\" cy=\"35\" r=\"1.4\" fill=\"#ec4899\" style=\"animation-delay:0.15s\"/>\n <circle class=\"synapse\" cx=\"50\" cy=\"70\" r=\"1.4\" fill=\"#18ffff\" style=\"animation-delay:0.75s\"/>\n </svg>\n </div>\n\n <div class=\"brand\">\n <div class=\"name\">Shadow Brain <span style=\"color:var(--text-3);font-size:10px;font-weight:600\">v7.0 Hive Mind</span></div>\n <div class=\"state\" id=\"brain-state\">learning \u00B7 reflecting \u00B7 charging</div>\n </div>\n\n <div class=\"header-stats\" id=\"header-stats\">\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-agents\">\u2014</span> agents</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-memories\">\u2014</span> memories</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-dreams\">\u2014</span> reflections</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-tools\">\u2014</span> tools</div>\n </div>\n\n <div class=\"header-right\">\n <span class=\"conn-dot\" id=\"conn-dot\" title=\"Realtime connection: connecting\"></span>\n <button class=\"kbd-hint\" onclick=\"openCmdPalette()\" title=\"Open command palette\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>\n Search <kbd>\u2318K</kbd>\n </button>\n <button class=\"icon-btn\" onclick=\"toggleTheme()\" title=\"Toggle theme\">\n <svg id=\"theme-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/><line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/><line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/><line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/><line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/><line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/><line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/></svg>\n </button>\n <button class=\"icon-btn\" onclick=\"toggleLogPanel()\" title=\"Activity log\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"21\" y1=\"10\" x2=\"3\" y2=\"10\"/><line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\"/><line x1=\"21\" y1=\"18\" x2=\"3\" y2=\"18\"/></svg></button>\n <button class=\"btn danger sm\" onclick=\"stopBrain()\" title=\"Stop Shadow Brain (npm process)\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1\"/></svg>Stop</button>\n <button class=\"btn primary sm\" onclick=\"refresh()\" title=\"Refresh all\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"23 4 23 10 17 10\"/><polyline points=\"1 20 1 14 7 14\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>Refresh</button>\n </div>\n</header>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Layout: sidebar + main\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div class=\"layout\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"mode-toggle\" id=\"mode-toggle\" title=\"Simple shows the essentials. Advanced reveals every feature.\">\n <button class=\"mode-btn active\" data-mode=\"simple\" onclick=\"setMode('simple')\">Simple</button>\n <button class=\"mode-btn\" data-mode=\"advanced\" onclick=\"setMode('advanced')\">Advanced</button>\n </div>\n\n <div class=\"nav-group\">\n <div class=\"nav-group-label\">Main</div>\n <button class=\"nav-item active\" data-tab=\"overview\" data-title=\"Home\" title=\"Your starting point \u2014 a quick summary of everything the brain knows and is doing.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/><polyline points=\"9 22 9 12 15 12 15 22\"/></svg>\n Home\n </button>\n <button class=\"nav-item\" data-tab=\"intelligence\" data-title=\"Brain Map\" title=\"A visual map of everything the brain remembers \u2014 memories, projects, and the tools that created them.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/><line x1=\"7\" y1=\"7\" x2=\"10\" y2=\"10.5\"/><line x1=\"17\" y1=\"7\" x2=\"14\" y2=\"10.5\"/><line x1=\"7\" y1=\"17\" x2=\"10\" y2=\"13.5\"/><line x1=\"17\" y1=\"17\" x2=\"14\" y2=\"13.5\"/></svg>\n Brain Map<span class=\"badge-count\" id=\"badge-intelligence\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"memory\" data-title=\"Memories\" title=\"Browse and search everything the brain has learned and saved.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M21 12c0 1.66-4 3-9 3s-9-1.34-9-3\"/><path d=\"M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5\"/></svg>\n Memories\n </button>\n <button class=\"nav-item\" data-tab=\"chat\" data-title=\"Ask the Brain\" title=\"Ask a question in plain language and get answers from the brain's memory.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n Ask the Brain\n </button>\n <button class=\"nav-item\" data-tab=\"activity\" data-title=\"Activity\" title=\"A live feed of what the brain and your tools are doing right now.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/></svg>\n Activity\n </button>\n <button class=\"nav-item\" data-tab=\"agents\" data-title=\"Connected Tools\" title=\"See which AI coding tools (Claude, Cursor, Codex\u2026) are connected to your brain.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n Connected Tools<span class=\"badge-count\" id=\"badge-agents\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"livebrain\" data-title=\"Brain Health\" title=\"A health check of your brain's memory quality, with tips to improve it.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>\n Brain Health\n </button>\n <button class=\"nav-item\" data-tab=\"config\" data-title=\"Settings\" title=\"Set up and adjust how Shadow Brain works.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n Settings\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Explore</div>\n <button class=\"nav-item\" data-tab=\"ww2br\" data-title=\"Project Memory\" title=\"Memory and status for one specific project.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 19V5\"/><path d=\"M4 6h9l1 2h6v8h-9l-1-2H4\"/></svg>\n Project Memory<span class=\"badge-count\" id=\"badge-ww2br\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"graph\" data-title=\"Live Network\" title=\"A live animation of your tools talking to the brain.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"12\" cy=\"12\" r=\"8\"/></svg>\n Live Network\n </button>\n <button class=\"nav-item\" data-tab=\"seeder\" data-title=\"Add Knowledge\" title=\"Research a topic and save it as verified, source-backed knowledge.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2v20\"/><path d=\"M5 9c0-3 2-5 7-7 5 2 7 4 7 7 0 5-4 8-7 8s-7-3-7-8z\"/><path d=\"M8 12h8\"/></svg>\n Add Knowledge<span class=\"badge-count\" id=\"badge-seeder\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"garden\" data-title=\"Memory Garden\" title=\"A relaxing visual of your memories growing over time.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s-8-4-8-12c0-4 3-7 8-7s8 3 8 7c0 8-8 12-8 12z\"/><path d=\"M12 17v-5M12 7V5\"/></svg>\n Memory Garden\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">How the Brain Thinks</div>\n <button class=\"nav-item\" data-tab=\"sabb\" data-title=\"Sub-Agents\" title=\"When a tool spawns helper agents, the brain shares the right context with them.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><line x1=\"8.59\" y1=\"13.51\" x2=\"15.42\" y2=\"17.49\"/><line x1=\"15.41\" y1=\"6.51\" x2=\"8.59\" y2=\"10.49\"/></svg>\n Sub-Agents<span class=\"badge-count\" id=\"badge-sabb\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"causal\" data-title=\"Decision Trails\" title=\"Trace why a decision was made \u2014 what led to it and what it caused.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>\n Decision Trails<span class=\"badge-count\" id=\"badge-causal\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"dream\" data-title=\"Reflections\" title=\"While idle, the brain reviews past work and finds patterns and lessons.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>\n Reflections<span class=\"badge-count\" id=\"badge-dream\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"debate\" data-title=\"Debates\" title=\"Have multiple AI viewpoints argue a tough decision, then get a verdict.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n Debates\n </button>\n <button class=\"nav-item\" data-tab=\"premortem\" data-title=\"Risk Check\" title=\"Before a big task, see what went wrong last time so you can avoid it.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>\n Risk Check\n </button>\n <button class=\"nav-item\" data-tab=\"branch\" data-title=\"Branch Memory\" title=\"Memory that follows your git branch, so each branch has its own context.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"6\" y1=\"3\" x2=\"6\" y2=\"15\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/></svg>\n Branch Memory\n </button>\n <button class=\"nav-item\" data-tab=\"forget\" data-title=\"Memory Cleanup\" title=\"Old, unused memories fade while important ones are kept \u2014 like human memory.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>\n Memory Cleanup\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Safety & Trust</div>\n <button class=\"nav-item\" data-tab=\"collision\" data-title=\"Conflicts\" title=\"Catches two tools about to edit the same code at the same time.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>\n Conflicts<span class=\"badge-count\" id=\"badge-collision\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"reputation\" data-title=\"Trust Scores\" title=\"How reliable each AI tool has been, based on its track record.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"8\" r=\"7\"/><polyline points=\"8.21 13.89 7 23 12 20 17 23 15.79 13.88\"/></svg>\n Trust Scores\n </button>\n <button class=\"nav-item\" data-tab=\"calibration\" data-title=\"Accuracy\" title=\"How well each tool's confidence matches how often it's actually right.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"/><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"/><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"/></svg>\n Accuracy\n </button>\n <button class=\"nav-item\" data-tab=\"attention\" data-title=\"Focus Map\" title=\"Shows which memories most influenced a particular decision.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"4\"/><line x1=\"21.17\" y1=\"8\" x2=\"12\" y2=\"8\"/></svg>\n Focus Map\n </button>\n <button class=\"nav-item\" data-tab=\"formal\" data-title=\"Code Rules\" title=\"Turn plain-language rules into checks your linters can enforce.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 11 12 14 22 4\"/><path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/></svg>\n Code Rules\n </button>\n <button class=\"nav-item\" data-tab=\"privacy\" data-title=\"Privacy & Safety\" title=\"Keep everything offline and block risky actions.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>\n Privacy & Safety\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Cost & Sharing</div>\n <button class=\"nav-item\" data-tab=\"tokens\" data-title=\"Cost & Usage\" title=\"Track how much your AI tools are spending and where you can save.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\"/></svg>\n Cost & Usage\n </button>\n <button class=\"nav-item\" data-tab=\"pr\" data-title=\"Code Review\" title=\"Generate a review for a pull request using what the brain knows.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"18\" cy=\"18\" r=\"3\"/><circle cx=\"6\" cy=\"6\" r=\"3\"/><path d=\"M13 6h3a2 2 0 0 1 2 2v7\"/><line x1=\"6\" y1=\"9\" x2=\"6\" y2=\"21\"/></svg>\n Code Review\n </button>\n <button class=\"nav-item\" data-tab=\"voice\" data-title=\"Voice\" title=\"Talk to the brain and hear answers, hands-free.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/></svg>\n Voice\n </button>\n <button class=\"nav-item\" data-tab=\"team\" data-title=\"Team Sync\" title=\"Share brain memory with your teammates.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n Team Sync\n </button>\n <button class=\"nav-item\" data-tab=\"exchange\" data-title=\"Share Brain\" title=\"Export a memory pack to share, or import one from someone else.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 3 21 3 21 8\"/><line x1=\"4\" y1=\"20\" x2=\"21\" y2=\"3\"/><polyline points=\"21 16 21 21 16 21\"/><line x1=\"15\" y1=\"15\" x2=\"21\" y2=\"21\"/><line x1=\"4\" y1=\"4\" x2=\"9\" y2=\"9\"/></svg>\n Share Brain\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">System</div>\n <button class=\"nav-item\" data-tab=\"features\" data-title=\"Features\" title=\"Turn individual brain features on or off.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"1\" y=\"5\" width=\"22\" height=\"14\" rx=\"7\"/><circle cx=\"16\" cy=\"12\" r=\"3\"/></svg>\n Features\n </button>\n <button class=\"nav-item\" data-tab=\"models\" data-title=\"AI Models\" title=\"Choose and configure which AI models the brain uses.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z\"/></svg>\n AI Models\n </button>\n </div>\n </aside>\n\n <main id=\"main\">\n\n <!-- \u2550\u2550\u2550\u2550 OVERVIEW \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section active\" id=\"tab-overview\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/><polyline points=\"9 22 9 12 15 12 15 22\"/></svg>Welcome to your Shadow Brain</h1>\n <div class=\"section-subtitle\">A shared memory for your AI coding tools \u2014 private, on your machine. Here's what it knows and is doing right now.</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"runDream()\" title=\"Have the brain review recent work and find patterns\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>Reflect Now</button>\n <button class=\"btn\" onclick=\"runForget()\" title=\"Tidy memory \u2014 fade old entries, keep the important ones\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>Clean Up</button>\n <button class=\"btn danger\" onclick=\"toggleAirGap()\" title=\"Block all outgoing network \u2014 fully offline\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>Go Offline</button>\n </div>\n </div>\n\n <div class=\"grid cols-4\">\n <div class=\"card stat-card accent\">\n <div class=\"label\">Tools Connected</div>\n <div class=\"value accent\" id=\"ov-agents\">\u2014</div>\n <div class=\"trend\">AI tools linked to your brain</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Memories Stored</div>\n <div class=\"value\" id=\"ov-memories\">\u2014</div>\n <div class=\"trend\" id=\"ov-memories-sub\">across all projects</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Helper Agents</div>\n <div class=\"value\" id=\"ov-subagents\">\u2014</div>\n <div class=\"trend\" id=\"ov-subagents-sub\">graduated / quarantined</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Active Tool</div>\n <div class=\"value\" style=\"font-size:22px\" id=\"ov-current-agent\">\u2014</div>\n <div class=\"trend\" id=\"ov-current-agent-sub\">using the brain right now</div>\n </div>\n </div>\n\n <div class=\"card accent span-full mt-lg const-hero\" onclick=\"switchTab('intelligence','')\" style=\"cursor:pointer\">\n <div class=\"const-hero-inner\">\n <div class=\"const-hero-art\">\n <svg viewBox=\"0 0 120 90\" xmlns=\"http://www.w3.org/2000/svg\">\n <line x1=\"60\" y1=\"45\" x2=\"24\" y2=\"20\" class=\"he\"/><line x1=\"60\" y1=\"45\" x2=\"98\" y2=\"22\" class=\"he\"/>\n <line x1=\"60\" y1=\"45\" x2=\"22\" y2=\"68\" class=\"he\"/><line x1=\"60\" y1=\"45\" x2=\"100\" y2=\"66\" class=\"he\"/>\n <line x1=\"60\" y1=\"45\" x2=\"60\" y2=\"14\" class=\"he\"/><line x1=\"24\" y1=\"20\" x2=\"22\" y2=\"68\" class=\"he\"/>\n <circle cx=\"60\" cy=\"45\" r=\"9\" fill=\"#18ffff\"/>\n <circle cx=\"24\" cy=\"20\" r=\"5\" fill=\"#10e68c\"/><circle cx=\"98\" cy=\"22\" r=\"5\" fill=\"#ffb53f\"/>\n <circle cx=\"22\" cy=\"68\" r=\"5\" fill=\"#a855f7\"/><circle cx=\"100\" cy=\"66\" r=\"4\" fill=\"#18ffff\"/>\n <circle cx=\"60\" cy=\"14\" r=\"4\" fill=\"#ec4899\"/>\n </svg>\n </div>\n <div class=\"const-hero-body\">\n <div class=\"const-hero-title\">Memory Constellation</div>\n <div class=\"const-hero-sub\">Explore every memory, project, agent & tag as a living, importance-ranked graph.</div>\n <div class=\"const-hero-stats\">\n <span><b id=\"hero-const-nodes\">\u2014</b> nodes</span>\n <span><b id=\"hero-const-edges\">\u2014</b> links</span>\n <span><b id=\"hero-const-mem\">\u2014</b> memories</span>\n </div>\n </div>\n <button class=\"btn primary lg\" onclick=\"event.stopPropagation();switchTab('intelligence','')\">Explore <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/></svg></button>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"/><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"/><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"/></svg>Reported Token Usage by Tool</div></div>\n <div class=\"chart-wrap\"><canvas id=\"chart-tokens\"></canvas></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>Recent Activity</div></div>\n <div id=\"ov-timeline\" style=\"max-height:220px;overflow-y:auto;font-size:11.5px\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card span-full accent\">\n <div class=\"card-header\">\n <div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/></svg>Live Pulse <span class=\"live-badge\" id=\"ov-pulse-badge\">LIVE</span></div>\n <span class=\"pill cyan\" id=\"ov-pulse-rate\">0 / min</span>\n </div>\n <div id=\"ov-pulse\" class=\"brain-feed\" style=\"max-height:300px;overflow-y:auto\"><div class=\"empty-tight\">Awaiting live activity\u2026</div></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Module Status</div></div>\n <div id=\"ov-modules\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">System Health</div></div>\n <div id=\"ov-health\"></div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 INTELLIGENCE (Memory Constellation + Analytics) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-intelligence\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/><line x1=\"7\" y1=\"7\" x2=\"10\" y2=\"10.5\"/><line x1=\"17\" y1=\"7\" x2=\"14\" y2=\"10.5\"/><line x1=\"7\" y1=\"17\" x2=\"10\" y2=\"13.5\"/><line x1=\"17\" y1=\"17\" x2=\"14\" y2=\"13.5\"/></svg>Intelligence</h1>\n <div class=\"section-subtitle\">Memory Constellation \u2014 every memory, project, agent & tag as a living graph, sized by importance</div>\n </div>\n <div class=\"section-actions\">\n <input id=\"const-search\" class=\"act-search\" placeholder=\"Search nodes\u2026\" oninput=\"highlightConstSearch()\">\n <button class=\"btn\" onclick=\"rebuildConstellation()\" title=\"Rebuild from the latest brain state\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"23 4 23 10 17 10\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>Rebuild</button>\n <button class=\"btn\" onclick=\"resetConstellationView()\" title=\"Reset zoom & pan\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"16\"/><line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"/></svg>Reset</button>\n </div>\n </div>\n\n <div class=\"filter-chips\" id=\"const-chips\">\n <button class=\"chip active\" data-ntype=\"\" onclick=\"setConstFilter(this,'')\">All</button>\n <button class=\"chip\" data-ntype=\"memory\" onclick=\"setConstFilter(this,'memory')\">Memories</button>\n <button class=\"chip\" data-ntype=\"project\" onclick=\"setConstFilter(this,'project')\">Projects</button>\n <button class=\"chip\" data-ntype=\"agent\" onclick=\"setConstFilter(this,'agent')\">Agents</button>\n <button class=\"chip\" data-ntype=\"tag\" onclick=\"setConstFilter(this,'tag')\">Tags</button>\n </div>\n\n <div class=\"card\" style=\"padding:0;overflow:hidden\">\n <div class=\"const-wrap\" id=\"const-wrap\">\n <canvas id=\"const-canvas\"></canvas>\n <div class=\"const-legend\">\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#18ffff\"></span>Memory</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#10e68c\"></span>Project</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#ffb53f\"></span>Agent</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#a855f7\"></span>Tag</div>\n </div>\n <div class=\"const-meta\" id=\"const-meta\">\u2014</div>\n <div class=\"const-hint\">scroll to zoom \u00B7 drag to pan \u00B7 click a node</div>\n <div class=\"const-empty\" id=\"const-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/></svg>\n <div class=\"ce-title\">No memories yet</div>\n <div class=\"ce-msg\">Attach Shadow Brain to your agents and let them work \u2014 memories will appear here as a living constellation.</div>\n </div>\n <div class=\"const-drawer\" id=\"const-drawer\">\n <button class=\"const-drawer-close\" onclick=\"closeConstDrawer()\" aria-label=\"Close\">\u00D7</button>\n <div id=\"const-drawer-body\"></div>\n </div>\n <div class=\"const-tip\" id=\"const-tip\"></div>\n </div>\n </div>\n\n <!-- Analytics strip -->\n <div class=\"grid cols-4 mt-lg\">\n <div class=\"card stat-card accent\"><div class=\"label\">Total Memories</div><div class=\"value accent\" id=\"intel-total\">\u2014</div><div class=\"trend\" id=\"intel-total-sub\">in the brain</div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">Source Coverage</div><div class=\"value\" id=\"intel-coverage\">\u2014</div><div class=\"meter mt\"><div class=\"fill\" id=\"intel-coverage-meter\" style=\"width:0%\"></div></div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">Fresh (24h)</div><div class=\"value\" id=\"intel-recent\">\u2014</div><div class=\"trend up\" id=\"intel-recent-sub\">new memories</div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">High Importance</div><div class=\"value\" id=\"intel-highimp\">\u2014</div><div class=\"trend\" id=\"intel-highimp-sub\">score \u2265 0.75</div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Agents by Contribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-intel-agents\"></canvas></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Memory Categories</div></div><div class=\"chart-wrap\"><canvas id=\"chart-intel-cats\"></canvas></div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Tags</div></div><div id=\"intel-tags\" class=\"tag-cloud\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Recent Memories</div></div><div id=\"intel-recent-list\" class=\"stack\" style=\"max-height:260px;overflow-y:auto\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 LIVE GRAPH \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- WW2BR BRAIN -->\n <section class=\"section\" id=\"tab-ww2br\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">WW2BR Brain View</h1>\n <div class=\"section-subtitle\">Live project memory, recall, and integration status for the current game-development workflow</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn primary\" onclick=\"refreshWW2BR()\">Refresh</button>\n </div>\n </div>\n\n <div class=\"workflow-strip\">\n <div>\n <div class=\"field\" style=\"margin:0\">\n <label>Active project path</label>\n <input class=\"workflow-path\" id=\"ww2-project-path\" value=\"D:\\New Game\"/>\n </div>\n </div>\n <button class=\"btn\" onclick=\"refreshWW2BR()\">Load</button>\n </div>\n\n <div id=\"ww2-warnings\" class=\"warning-stack\"></div>\n\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\">\n <div class=\"label\">Global Memories</div>\n <div class=\"value accent\" id=\"ww2-stat-memories\">0</div>\n <div class=\"trend\" id=\"ww2-stat-scope\">project scoped</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Brain Projects</div>\n <div class=\"value\" id=\"ww2-stat-projects\">0</div>\n <div class=\"trend\">known roots</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Agent Hooks</div>\n <div class=\"value\" id=\"ww2-stat-hooks\">0</div>\n <div class=\"trend\">wired for this path</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Hit Rate</div>\n <div class=\"value\" id=\"ww2-stat-hit\">0%</div>\n <div class=\"trend\" id=\"ww2-stat-sync\">last sync unknown</div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Memory Quality</div></div>\n <div id=\"ww2-quality\" class=\"stack\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\">\n <div class=\"card-title\">Context Continuity Pack</div>\n <button class=\"btn sm\" onclick=\"openWW2ResumePack()\">Markdown</button>\n </div>\n <div id=\"ww2-resume-pack\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card span-2\">\n <div class=\"card-header\">\n <div class=\"card-title\">Recent Brain Timeline</div>\n <button class=\"btn sm\" onclick=\"refreshWW2BR()\">Refresh</button>\n </div>\n <div id=\"ww2-timeline\" class=\"brain-feed\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Integrations</div></div>\n <div id=\"ww2-agents\" class=\"ops-agent-grid\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Memory Categories</div></div>\n <div id=\"ww2-categories\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Recall Query</div></div>\n <div class=\"grid cols-3\">\n <div class=\"field span-2\"><label>Query</label><input id=\"ww2-query\" placeholder=\"Gate 2, operator proof, lobby blocker\"/></div>\n <div class=\"field\"><label>Category</label><input id=\"ww2-category\" placeholder=\"optional\"/></div>\n </div>\n <div class=\"flex gap\">\n <button class=\"btn primary\" onclick=\"runWW2Recall()\">Recall</button>\n <button class=\"btn\" onclick=\"setWW2Query('Gate 2 visual proof')\">Gate 2</button>\n <button class=\"btn\" onclick=\"setWW2Query('operator hands rifle hold')\">Operator</button>\n <button class=\"btn\" onclick=\"setWW2Query('lobby verification blockers')\">Lobby</button>\n </div>\n <div id=\"ww2-recall-results\" class=\"brain-feed mt\"></div>\n </div>\n </div>\n </section>\n\n <section class=\"section\" id=\"tab-graph\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Live Agent Network</h1>\n <div class=\"section-subtitle\">Real-time signal flow: brain \u2194 agents \u2194 sub-agents</div>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <div class=\"graph-wrap\"><canvas id=\"graph-canvas\" width=\"1600\" height=\"600\"></canvas></div>\n <div style=\"padding:10px 14px;display:flex;gap:16px;flex-wrap:wrap;font-size:11px;color:var(--text-2)\">\n <span><span style=\"color:var(--success)\">\u25CF</span> memory-write</span>\n <span><span style=\"color:var(--brand-cyan)\">\u25CF</span> subagent-spawn</span>\n <span><span style=\"color:var(--brand-purple)\">\u25CF</span> dream</span>\n <span><span style=\"color:var(--danger)\">\u25CF</span> collision</span>\n <span><span style=\"color:var(--warning)\">\u25CF</span> firewall-block</span>\n <span id=\"graph-meta\" style=\"margin-left:auto;color:var(--text-3)\"></span>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 AGENTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-agents\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Agent Connections</h1>\n <div class=\"section-subtitle\">Connect or disconnect Shadow Brain hooks per agent \u00B7 per-agent native mechanisms</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn success\" onclick=\"attachAll()\">Attach All</button>\n <button class=\"btn danger\" onclick=\"detachAll()\">Detach All</button>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\">\n <thead><tr><th>Agent</th><th>Detected</th><th>Hook Status</th><th>Install Path</th><th style=\"text-align:right\">Action</th></tr></thead>\n <tbody id=\"agents-table\"></tbody>\n </table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CHAT WITH BRAIN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-chat\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>Chat with Your Brain</h1>\n <div class=\"section-subtitle\">Ask anything \u2014 Shadow Brain retrieves relevant memories via semantic search and cites them. Uses your configured lead model.</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"newChat()\">New Chat</button>\n <button class=\"btn danger\" onclick=\"clearAllChats()\">Clear All</button>\n </div>\n </div>\n <div class=\"grid\" style=\"grid-template-columns: 260px 1fr; gap: 14px\">\n <div class=\"card\" style=\"height: calc(100vh - 220px); overflow-y: auto; padding: 12px\">\n <div class=\"card-title\" style=\"margin-bottom: 10px\">Conversations</div>\n <div id=\"chat-conversations\"></div>\n </div>\n <div class=\"card\" style=\"height: calc(100vh - 220px); display: flex; flex-direction: column; padding: 0\">\n <div id=\"chat-messages\" style=\"flex: 1; overflow-y: auto; padding: 16px\"></div>\n <div style=\"border-top: 1px solid var(--border); padding: 12px; display: flex; gap: 8px\">\n <input id=\"chat-input\" placeholder=\"Ask your brain \u2014 e.g. 'What did Cursor decide about bcrypt?'\" style=\"flex:1;background:var(--bg-elev-1);border:1px solid var(--border);border-radius:8px;padding:10px 14px;color:var(--text);font-size:13px\" onkeydown=\"if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}\"/>\n <button class=\"btn primary\" onclick=\"sendChat()\" id=\"chat-send-btn\">Send</button>\n </div>\n <div class=\"mono\" style=\"padding: 4px 16px 10px; font-size: 10px; color: var(--text-3)\" id=\"chat-status\">idle</div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 MEMORY BROWSER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-memory\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Memory Browser</h1>\n <div class=\"section-subtitle\">Full-text search plus live memory quality, provenance, freshness, and agent coverage</div>\n </div>\n </div>\n <div class=\"card\">\n <div class=\"grid cols-4\">\n <div class=\"field\"><label>Search query</label><input id=\"mem-query\" placeholder=\"e.g. auth, react, bcrypt\u2026\"/></div>\n <div class=\"field\"><label>Agent</label><select id=\"mem-agent\"><option value=\"\">all agents</option><option>claude-code</option><option>cursor</option><option>codex</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Category</label><input id=\"mem-category\" placeholder=\"e.g. security\"/></div>\n <div class=\"field\"><label>Min importance</label><input id=\"mem-importance\" type=\"number\" min=\"0\" max=\"1\" step=\"0.1\" value=\"0\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"searchMemory()\">Search</button>\n </div>\n <div class=\"card mt\">\n <table class=\"table\"><thead><tr><th>ID</th><th>Agent</th><th>Category</th><th>Content</th><th>Importance</th><th>Created</th></tr></thead>\n <tbody id=\"mem-results\"></tbody>\n </table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 LIVE BRAIN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- MEMORY SEEDER -->\n <section class=\"section\" id=\"tab-seeder\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Memory Seeder</h1>\n <div class=\"section-subtitle\">Research drafts, approval gates, and source-backed global brain writes</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"refreshSeedDrafts()\">Refresh</button>\n </div>\n </div>\n\n <div class=\"grid cols-3\">\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Seed Research Draft</div></div>\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Project path</label><input id=\"seed-project-path\" placeholder=\"C:\\path\\to\\project\"/></div>\n <div class=\"field\"><label>Topic</label><input id=\"seed-topic\" placeholder=\"memory routing, architecture, release, debugging\"/></div>\n </div>\n <div class=\"field\"><label>Objective</label><input id=\"seed-objective\" placeholder=\"what the next agent must learn and reuse\"/></div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Research agent</label><select id=\"seed-research-agent\"><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Approver</label><select id=\"seed-approver-agent\"><option>user</option><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Approval mode</label><select id=\"seed-approval-mode\"><option>user</option><option>agent</option><option>dual</option></select></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Category</label><input id=\"seed-category\" value=\"project.research\"/></div>\n <div class=\"field\"><label>Tags</label><input id=\"seed-tags\" placeholder=\"enterprise,memory,release\"/></div>\n <div class=\"field\"><label>Importance</label><input id=\"seed-importance\" type=\"number\" min=\"0.05\" max=\"1\" step=\"0.05\" value=\"0.78\"/></div>\n </div>\n <div class=\"field\"><label>Sources</label><textarea id=\"seed-sources\" rows=\"2\" placeholder=\"README.md, CHANGELOG.md, docs/launch/RELEASE_NOTES_v6.0.3.md\"></textarea></div>\n <div class=\"field\"><label>Research notes</label><textarea id=\"seed-content\" rows=\"4\" placeholder=\"facts, constraints, tradeoffs, exact commands, source-backed decisions\"></textarea></div>\n <div class=\"flex gap\">\n <button class=\"btn primary\" onclick=\"quickSeed()\" title=\"Create, approve, and save to the brain in one step\">\u26A1 Quick Save</button>\n <button class=\"btn\" onclick=\"createSeedDraft()\" title=\"Create a draft that waits for approval\">Create Draft (review first)</button>\n <button class=\"btn\" onclick=\"copyLivePathToSeeder()\">Use Live Brain Path</button>\n </div>\n <div class=\"section-subtitle\" style=\"margin-top:8px\">Quick Save is the fast, one-click path. Use \"Create Draft\" when you want a review/approval step before it's saved.</div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Seeder Status</div></div>\n <div id=\"seed-status\" class=\"stack\"></div>\n </div>\n </div>\n\n <div class=\"card mt-lg\">\n <div class=\"card-header\">\n <div class=\"card-title\">Approval Queue</div>\n <button class=\"btn sm\" onclick=\"refreshSeedDrafts()\">Refresh</button>\n </div>\n <div id=\"seed-drafts\" class=\"brain-feed\"></div>\n </div>\n </section>\n\n <section class=\"section\" id=\"tab-livebrain\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Live Brain</h1>\n <div class=\"section-subtitle\">Memory health, source coverage, recall eval, and agent briefing for the current workspace</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"openSeederForLiveBrain()\">Seed Memory</button>\n <button class=\"btn primary\" onclick=\"refreshLiveBrain()\">Refresh</button>\n </div>\n </div>\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\"><div class=\"label\">Total Memories</div><div class=\"value\" id=\"mem-total\">0</div><div class=\"trend\" id=\"mem-sampled\">sampled</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Source Coverage</div><div class=\"value\" id=\"mem-source\">0%</div><div class=\"trend\">evidence-backed records</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Fresh 7 Days</div><div class=\"value\" id=\"mem-fresh\">0</div><div class=\"trend\">recent learning</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Seed Queue</div><div class=\"value\" id=\"mem-seed-open\">0</div><div class=\"trend\" id=\"mem-seed-sub\">approval queue</div></div>\n </div>\n <div class=\"grid cols-3 mt\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Categories</div></div><div id=\"mem-categories\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Agent Coverage</div></div><div id=\"mem-agents\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Tags</div></div><div id=\"mem-tags\"></div></div>\n </div>\n <div class=\"card mt\">\n <div class=\"field-row\">\n <div class=\"field\"><label>Project path</label><input id=\"live-project-path\" placeholder=\"C:\\path\\to\\project\"/></div>\n <div class=\"field\"><label>Eval queries</label><input id=\"live-queries\" placeholder=\"current blocker|do not repeat|verified command\"/></div>\n </div>\n </div>\n <div class=\"grid cols-4 mt\">\n <div class=\"card stat-card\"><div class=\"label\">Memories</div><div class=\"value\" id=\"live-memories\">0</div><div class=\"trend\" id=\"live-scope\">scope</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Quality</div><div class=\"value\" id=\"live-quality\">0</div><div class=\"trend\">average score</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Sources</div><div class=\"value\" id=\"live-sources\">0%</div><div class=\"trend\">coverage</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Recall</div><div class=\"value\" id=\"live-recall\">0</div><div class=\"trend\">avg hits/query</div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Recommended Actions</div></div>\n <div id=\"live-actions\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Recall Eval</div></div>\n <div id=\"live-eval\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Top Memories</div></div>\n <div id=\"live-top\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Briefing</div></div>\n <pre class=\"mono\" id=\"live-brief\" style=\"max-height:360px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px;white-space:pre-wrap\"></pre>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 ACTIVITY LOG \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-activity\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Activity Log <span class=\"live-badge\" id=\"act-live-badge\" title=\"Streaming live over WebSocket\">LIVE</span></h1>\n <div class=\"section-subtitle\">Realtime stream: memory writes, SABB spawns, collisions, dreams, seeds, consolidations, firewall blocks</div>\n </div>\n <div class=\"section-actions\">\n <input id=\"act-search\" class=\"act-search\" placeholder=\"Search activity\u2026\" oninput=\"renderActivityTable()\">\n <button class=\"btn\" id=\"act-pause-btn\" onclick=\"toggleActivityPause()\" title=\"Pause/resume the live stream\">Pause</button>\n <button class=\"btn danger\" onclick=\"clearActivity()\" title=\"Clear the activity view\">Clear</button>\n <button class=\"btn\" onclick=\"refreshActivity()\">Refresh</button>\n </div>\n </div>\n <div class=\"filter-chips\" id=\"act-chips\">\n <button class=\"chip active\" data-type=\"\" onclick=\"setActivityFilter(this,'')\">All</button>\n <button class=\"chip\" data-type=\"memory-write\" onclick=\"setActivityFilter(this,'memory-write')\">Memory</button>\n <button class=\"chip\" data-type=\"dream\" onclick=\"setActivityFilter(this,'dream')\">Dreams</button>\n <button class=\"chip\" data-type=\"seed-draft\" onclick=\"setActivityFilter(this,'seed-draft')\">Seeds</button>\n <button class=\"chip\" data-type=\"collision\" onclick=\"setActivityFilter(this,'collision')\">Collisions</button>\n <button class=\"chip\" data-type=\"subagent-spawn\" onclick=\"setActivityFilter(this,'subagent-spawn')\">Sub-Agents</button>\n <button class=\"chip\" data-type=\"consolidation\" onclick=\"setActivityFilter(this,'consolidation')\">Consolidation</button>\n </div>\n <div class=\"grid cols-2 mb\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Events / min</div><span class=\"pill cyan\" id=\"act-rate-pill\">0 / min</span></div>\n <div class=\"chart-wrap\" style=\"height:120px\"><canvas id=\"act-spark\"></canvas></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Stream Stats</div></div>\n <div class=\"row\"><span class=\"k\">Events buffered</span><span class=\"v\" id=\"act-stat-total\">0</span></div>\n <div class=\"row\"><span class=\"k\">Live status</span><span class=\"v good\" id=\"act-stat-live\">streaming</span></div>\n <div class=\"row\"><span class=\"k\">Last event</span><span class=\"v\" id=\"act-stat-last\">\u2014</span></div>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\"><thead><tr><th>Time</th><th>Source</th><th>Type</th><th>Detail</th></tr></thead><tbody id=\"activity-table\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 SABB \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-sabb\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Sub-Agent Brain Bridge</h1>\n <div class=\"section-subtitle\">Context slivers + quarantine for Claude Task / Cursor Composer / CrewAI / LangGraph / AutoGen</div>\n </div>\n </div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">SABB Stats</div></div>\n <div id=\"sabb-stats\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Compute Context Sliver</div></div>\n <div class=\"field\"><label>Parent agent</label><select id=\"sabb-parent\"><option>claude-code</option><option>cursor</option><option>cline</option><option>codex</option></select></div>\n <div class=\"field\"><label>Framework</label><select id=\"sabb-framework\"><option>claude-code-task</option><option>cursor-composer</option><option>cline-substep</option><option>crewai</option><option>langgraph</option><option>autogen</option><option>generic</option></select></div>\n <div class=\"field\"><label>Task description</label><input id=\"sabb-task\" placeholder=\"e.g. refactor auth middleware\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Token budget</label><input id=\"sabb-budget\" value=\"300\"/></div>\n <div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"computeSliver()\" style=\"width:100%\">Generate</button></div>\n </div>\n <pre class=\"mono\" id=\"sabb-sliver-output\" style=\"margin-top:10px;max-height:220px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Quarantined Memories</div></div>\n <table class=\"table\"><thead><tr><th>ID</th><th>Sub-Agent</th><th>Category</th><th>Conf.</th><th>Content</th><th style=\"text-align:right\">Action</th></tr></thead><tbody id=\"sabb-quarantine\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CAUSAL CHAINS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-causal\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div><h1 class=\"section-title\">Causal Memory Chains</h1><div class=\"section-subtitle\">Trace the full causal history of any decision</div></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Stats</div></div>\n <div id=\"causal-stats\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Trace Ancestors / Descendants</div></div>\n <div class=\"field\"><label>Memory ID</label><input id=\"causal-mem-id\" placeholder=\"abc123\u2026\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Max depth</label><input id=\"causal-depth\" value=\"8\"/></div>\n <div class=\"field\"><label> </label><div style=\"display:flex;gap:6px\"><button class=\"btn primary\" onclick=\"traceCausal()\" style=\"flex:1\">Ancestors</button><button class=\"btn\" onclick=\"influenceCausal()\" style=\"flex:1\">Descendants</button></div></div>\n </div>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Causal Chain Graph</div></div>\n <div class=\"dag-wrap\" id=\"causal-dag-wrap\" style=\"margin:0 14px 14px\">\n <canvas id=\"causal-dag\"></canvas>\n <div class=\"dag-empty\" id=\"causal-dag-empty\">Enter a memory ID and trace ancestors or descendants to render its causal chain.</div>\n </div>\n </div>\n <div class=\"card mt-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Record a Causal Link</div></div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Effect ID</label><input id=\"causal-effect\"/></div>\n <div class=\"field\"><label>Cause ID</label><input id=\"causal-cause\"/></div>\n <div class=\"field\"><label>Rationale (optional)</label><input id=\"causal-rationale\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"linkCausal()\">Record Link</button>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 COLLISION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-collision\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Agent Collision Detective</h1><div class=\"section-subtitle\">Real-time conflict detection across agent edit intents</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Stats</div></div><div id=\"collision-stats\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Active Alerts</div></div><div id=\"collision-alerts\"></div></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Active Edit Intents</div></div>\n <table class=\"table\"><thead><tr><th>Agent</th><th>File</th><th>Lines</th><th>Intent</th><th>Expires</th></tr></thead><tbody id=\"collision-intents\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 DREAM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-dream\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div><h1 class=\"section-title\">Dream Engine</h1><div class=\"section-subtitle\">Background idle reflection \u2014 local-first via Ollama</div></div>\n <div class=\"section-actions\"><button class=\"btn primary\" onclick=\"runDream()\">Run Cycle</button><button class=\"btn\" onclick=\"startDreamLoop()\">Start Loop</button></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Stats</div></div><div id=\"dream-stats\"></div></div>\n <div class=\"card span-2\"><div class=\"card-header\"><div class=\"card-title\">Dream Type Distribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-dreams\"></canvas></div></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Recent Dreams</div></div>\n <table class=\"table\"><thead><tr><th>Type</th><th>Content</th><th>Conf.</th><th>Action</th><th></th></tr></thead><tbody id=\"dream-list\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 REPUTATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-reputation\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Agent Reputation Ledger</h1><div class=\"section-subtitle\">Ed25519-signed decision receipts \u00B7 portable trust score</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Ledger Stats</div></div><div id=\"reputation-stats\"></div></div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Generate Badge</div></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Agent</label><select id=\"rep-agent\"><option>claude-code</option><option>cursor</option><option>codex</option><option>cline</option></select></div>\n <div class=\"field\"><label>Version</label><input id=\"rep-ver\" placeholder=\"4.7\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"genBadge()\">Generate</button>\n <pre class=\"mono\" id=\"rep-badge-output\" style=\"margin-top:10px;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n <div class=\"card mt-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Trust</div></div>\n <div id=\"rep-trust-grid\" class=\"trust-grid\"></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Agent Leaderboard</div></div>\n <table class=\"table\"><thead><tr><th>Agent</th><th>Accuracy</th><th>Decisions</th><th>Correct/Incorrect/Partial</th></tr></thead><tbody id=\"rep-leaderboard\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 DEBATE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-debate\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Swarm Debate Protocol</h1><div class=\"section-subtitle\">Pro / con / arbiter multi-agent debate for critical decisions</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Question</label><input id=\"debate-question\" placeholder=\"e.g. Redis or Postgres for session storage?\"/></div>\n <div class=\"field\"><label>Context</label><textarea id=\"debate-context\" rows=\"2\" placeholder=\"e.g. 10k users, read-heavy workload\"></textarea></div>\n <div class=\"field-row\"><div class=\"field\"><label>Turns per side</label><input id=\"debate-turns\" value=\"2\"/></div><div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"runDebate()\" style=\"width:100%\">Run Debate</button></div></div>\n <pre class=\"mono\" id=\"debate-output\" style=\"margin-top:12px;max-height:420px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PREMORTEM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-premortem\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Pre-Mortem Assistant</h1><div class=\"section-subtitle\">Surface past failures from YOUR project before significant tasks</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Task description</label><input id=\"pm-task\" placeholder=\"e.g. migrate auth to passkeys\"/></div>\n <button class=\"btn primary\" onclick=\"runPremortem()\">Run Pre-Mortem</button>\n <div id=\"pm-output\" style=\"margin-top:14px\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 ATTENTION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-attention\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Attention Heatmap</h1><div class=\"section-subtitle\">See which memories shaped a decision \u2014 weighted attribution</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Decision text</label><input id=\"att-decision\" placeholder=\"e.g. use parameterized SQL queries\"/></div>\n <div class=\"field\"><label>Candidate memory IDs (comma-separated, optional \u2014 blank uses recent)</label><input id=\"att-memories\" placeholder=\"id1, id2, id3\"/></div>\n <button class=\"btn primary\" onclick=\"runAttention()\">Compute Attention</button>\n <div id=\"att-output\" style=\"margin-top:14px\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 TOKENS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-tokens\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Token Economy Engine</h1><div class=\"section-subtitle\">Cross-agent spend tracking \u00B7 savings suggestions \u00B7 routing advice</div></div></div>\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\"><div class=\"label\">Current Agent</div><div class=\"value\" id=\"tok-agent\">\u2014</div><div class=\"trend\" id=\"tok-runtime\">runtime context</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Total Spend</div><div class=\"value\" id=\"tok-total\">$\u2014</div><div class=\"trend\">all historical records</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Last 7 Days</div><div class=\"value\" id=\"tok-recent\">$\u2014</div><div class=\"trend\">recent records only</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Total Calls</div><div class=\"value\" id=\"tok-calls\">\u2014</div><div class=\"trend\" id=\"tok-calls-sub\"></div></div>\n </div>\n <div class=\"grid cols-2 mt\">\n <div class=\"card stat-card\"><div class=\"label\">Monthly Projection</div><div class=\"value\" id=\"tok-proj\">$\u2014</div><div class=\"trend\">from last 7 days</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Savings Available</div><div class=\"value\" style=\"color:var(--success)\" id=\"tok-save\">$\u2014</div><div class=\"trend up\">via smart routing</div></div>\n </div>\n <div class=\"warning-banner mt\" id=\"tok-runtime-note\"><strong>Accuracy</strong><span>Token records are historical. Current agent and model spend are shown separately.</span></div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Recent Spend by Agent</div></div><div class=\"chart-wrap\"><canvas id=\"chart-tokens-agent\"></canvas></div><div id=\"tok-agent-table\" class=\"mt\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Historical Spend by Model</div></div><div class=\"chart-wrap\"><canvas id=\"chart-tokens-model\"></canvas></div><div id=\"tok-model-table\" class=\"mt\"></div></div>\n <div class=\"card span-2\"><div class=\"card-header\"><div class=\"card-title\">Savings Suggestions</div></div><div id=\"tok-suggestions\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FORGET \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-forget\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Forgetting Curve + Sleep Consolidation</h1><div class=\"section-subtitle\">Ebbinghaus-inspired natural decay \u00B7 tier promotion</div></div><div class=\"section-actions\"><button class=\"btn primary\" onclick=\"runForget()\">Run Consolidation Cycle</button></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Last Cycle</div></div><div id=\"forget-output\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Memory Strength Distribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-forget\"></canvas></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FORMAL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-formal\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Formal Verification Bridge</h1><div class=\"section-subtitle\">Natural-language rules \u2192 ESLint / Semgrep / LSP diagnostics</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Generate Rule</div></div>\n <div class=\"field\"><label>Natural-language rule</label><textarea id=\"formal-text\" rows=\"3\" placeholder=\"e.g. always use parameterized SQL queries\"></textarea></div>\n <button class=\"btn primary\" onclick=\"genFormal()\">Generate</button>\n <pre class=\"mono\" id=\"formal-output\" style=\"margin-top:12px;max-height:220px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Exports</div></div>\n <div class=\"flex gap\"><button class=\"btn\" onclick=\"exportEslint()\">ESLint</button><button class=\"btn\" onclick=\"exportSemgrep()\">Semgrep YAML</button></div>\n <pre class=\"mono\" id=\"formal-export\" style=\"margin-top:12px;max-height:280px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CALIBRATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-calibration\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Confidence Calibration Monitor</h1><div class=\"section-subtitle\">Brier scores & trust weight per agent per category</div></div></div>\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Brier Score vs Trust Weight by Agent</div></div>\n <div class=\"chart-wrap\"><canvas id=\"chart-calibration\"></canvas></div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\"><thead><tr><th>Agent</th><th>Category</th><th>Brier</th><th>Cal. Error</th><th>Overconf.</th><th>Trust Weight</th><th>Samples</th></tr></thead><tbody id=\"calibration-table\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 BRANCH \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-branch\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Branch Brains</h1><div class=\"section-subtitle\">Git-branch-aware memory context</div></div></div>\n <div class=\"grid cols-4\" id=\"branch-cards\"></div>\n <div class=\"card mt-lg\"><div class=\"card-header\"><div class=\"card-title\">Branch-Specific Categories</div></div><div id=\"branch-cats\" class=\"tag-cloud\"></div></div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PRIVACY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-privacy\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Privacy & Safety</h1><div class=\"section-subtitle\">Air-Gap mode \u00B7 encryption \u00B7 hallucination quarantine</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Air-Gap Mode</div></div>\n <div id=\"airgap-state\"></div>\n <div class=\"flex gap mt\"><button class=\"btn success\" onclick=\"enableAirGap()\">Enable (strict)</button><button class=\"btn danger\" onclick=\"disableAirGap()\">Disable</button></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Hallucination Quarantine</div></div>\n <div id=\"quarantine-state\"></div>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Pending Quarantine Queue</div></div>\n <table class=\"table\"><thead><tr><th>ID</th><th>Source</th><th>Claim</th><th>Reason</th><th style=\"text-align:right\">Action</th></tr></thead><tbody id=\"quarantine-list\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 VOICE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-voice\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Voice Mode</h1><div class=\"section-subtitle\">Process voice transcripts \u00B7 local Whisper-compatible</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Transcript</label><input id=\"voice-input\" placeholder=\"e.g. brain status\"/></div>\n <button class=\"btn primary\" onclick=\"sendVoice()\">Process</button>\n <pre class=\"mono\" id=\"voice-output\" style=\"margin-top:12px;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 GARDEN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-garden\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Brain Garden</h1><div class=\"section-subtitle\">Living constellation visualization of the brain</div></div></div>\n <div class=\"card\">\n <canvas id=\"garden-canvas\" width=\"1200\" height=\"500\" style=\"width:100%;background:radial-gradient(ellipse at center, rgba(24,255,255,0.04), transparent);border-radius:8px;display:block\"></canvas>\n <div class=\"mono mt\" id=\"garden-stats\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PR REVIEW \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-pr\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">PR Auto-Review</h1><div class=\"section-subtitle\">Generate GitHub PR comments from brain memories</div></div></div>\n <div class=\"card\">\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Repo (owner/name)</label><input id=\"pr-repo\" placeholder=\"theihtisham/agent-shadow-brain\"/></div>\n <div class=\"field\"><label>PR number</label><input id=\"pr-num\" placeholder=\"42\"/></div>\n </div>\n <div class=\"field\"><label>Changed files (comma-separated)</label><input id=\"pr-files\"/></div>\n <div class=\"field\"><label>Diff summary</label><textarea id=\"pr-diff\" rows=\"4\"></textarea></div>\n <button class=\"btn primary\" onclick=\"genPRReview()\">Generate Review</button>\n <pre class=\"mono\" id=\"pr-output\" style=\"margin-top:14px;max-height:400px;overflow:auto;background:var(--bg-elev-1);padding:14px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 TEAM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-team\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Team Brain Sync</h1><div class=\"section-subtitle\">Peer-to-peer brain sync (WebRTC, no server)</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">My Peer Info</div></div><div id=\"team-self\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Connected Peers</div></div><div id=\"team-peers\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 EXCHANGE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-exchange\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Brain Exchange</h1><div class=\"section-subtitle\">Export / import curated shareable brain slices</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Export Package</div></div>\n <div class=\"field\"><label>Name</label><input id=\"ex-name\" placeholder=\"react-patterns-v1\"/></div>\n <div class=\"field\"><label>Description</label><input id=\"ex-desc\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Author</label><input id=\"ex-author\" value=\"anonymous\"/></div>\n <div class=\"field\"><label>Tags (csv)</label><input id=\"ex-tags\" placeholder=\"react, auth\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"exportPackage()\">Export</button>\n <div class=\"mono mt\" id=\"ex-out\"></div>\n </div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Local Packages</div></div><div id=\"ex-list\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FEATURE TOGGLES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-features\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Feature Toggles</h1><div class=\"section-subtitle\">Enable / disable any v6 module \u00B7 persisted globally</div></div></div>\n <div class=\"card\"><div class=\"feature-grid\" id=\"feature-grid\"></div></div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 MODELS & INTELLIGENCE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-models\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Models & Brain Intelligence</h1>\n <div class=\"section-subtitle\">Route the Hive Mind through Ollama \u00B7 Anthropic \u00B7 OpenAI \u00B7 OpenRouter \u00B7 Moonshot/Kimi \u00B7 Gemini \u00B7 DeepSeek \u00B7 Mistral \u2014 or reuse your existing agent tools' configuration</div>\n </div>\n <div class=\"section-actions\"><button class=\"btn\" onclick=\"refreshModels()\">Refresh</button></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Auto-Discovered Agent Configurations</div></div>\n <div class=\"mono mb\" style=\"color:var(--text-2)\">Shadow Brain detected these agent tools' configs on your machine \u2014 no need to re-enter API keys. Toggle \"Use agent-tool models\" below to let the brain borrow their intelligence.</div>\n <div id=\"discovered-agents\"></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Brain Intelligence Configuration</div></div>\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Lead Provider</label><select id=\"intel-provider\"></select></div>\n <div class=\"field\"><label>Lead Model</label><select id=\"intel-model\"></select></div>\n </div>\n <div class=\"feature-toggle mb\"><div class=\"icon\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg></div><div class=\"meta\"><div class=\"name\">Use already-configured agent-tool models</div><div class=\"desc\">Reuse Claude Code / Cursor / Codex / Cline intelligence \u2014 no new API keys needed</div></div><label class=\"switch\"><input type=\"checkbox\" id=\"intel-agent-proxy\"><span class=\"slider\"></span></label></div>\n <div class=\"feature-toggle\"><div class=\"icon\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg></div><div class=\"meta\"><div class=\"name\">Prefer Local-First (Ollama)</div><div class=\"desc\">Route everything through Ollama when possible \u00B7 zero cost \u00B7 offline-ready</div></div><label class=\"switch\"><input type=\"checkbox\" id=\"intel-local-first\"><span class=\"slider\"></span></label></div>\n <button class=\"btn primary mt\" onclick=\"saveIntelligence()\">Save Intelligence Config</button>\n <div class=\"mono mt\" id=\"intel-result\"></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Provider Configuration</div></div>\n <div class=\"grid cols-2\" id=\"providers-grid\"></div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Custom Agent Paths (Hermes / OpenClaw / Nemoclaw / any)</div></div>\n <div class=\"mono mb\" style=\"color:var(--text-2)\">Point Shadow Brain at any agent's config file \u2014 we'll read the provider, model, and API key if present.</div>\n <div id=\"custom-agents-list\"></div>\n <div class=\"grid cols-3 mt\">\n <div class=\"field\"><label>Agent name</label><input id=\"ca-name\" placeholder=\"hermes / openclaw / nemoclaw / ...\"/></div>\n <div class=\"field\"><label>Config file path</label><input id=\"ca-path\" placeholder=\"C:\\Users\\you\\.hermes\\config.json\"/></div>\n <div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"addCustomAgent()\" style=\"width:100%\">Add</button></div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CONFIG \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-config\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Configuration</h1><div class=\"section-subtitle\">LLM provider \u00B7 API keys \u00B7 MCP server \u00B7 global settings</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">LLM Provider</div></div>\n <div class=\"field\"><label>Provider</label><select id=\"cfg-provider\"><option value=\"ollama\">Ollama (local)</option><option value=\"anthropic\">Anthropic</option><option value=\"openai\">OpenAI</option><option value=\"gemini\">Gemini</option><option value=\"deepseek\">DeepSeek</option></select></div>\n <div class=\"field\"><label>API Key (blank = env var)</label><input type=\"password\" id=\"cfg-apikey\"/></div>\n <div class=\"field\"><label>Model</label><input id=\"cfg-model\" placeholder=\"claude-opus-4-7\"/></div>\n <div class=\"flex gap\"><button class=\"btn primary\" onclick=\"saveConfig()\">Save</button><button class=\"btn\" onclick=\"testConnection()\">Test</button></div>\n <div class=\"mono mt\" id=\"cfg-result\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">MCP Server</div></div>\n <div class=\"field\"><label>Port</label><input id=\"mcp-port\" value=\"7342\"/></div>\n <div class=\"field\"><label>Auth Token (optional)</label><input id=\"mcp-token\" type=\"password\"/></div>\n <div class=\"flex gap\"><button class=\"btn primary\" onclick=\"startMCP()\">Start</button><button class=\"btn danger\" onclick=\"stopMCP()\">Stop</button></div>\n <div class=\"mono mt\" id=\"mcp-result\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">System Status</div></div>\n <div id=\"config-state\"></div>\n </div>\n </div>\n </section>\n\n </main>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Command Palette overlay (Cmd+K)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div class=\"cmd-overlay\" id=\"cmd-overlay\" onclick=\"if(event.target===this)closeCmdPalette()\">\n <div class=\"cmd-palette\">\n <input id=\"cmd-input\" placeholder=\"Search features, actions, memories\u2026\" autocomplete=\"off\"/>\n <div class=\"results\" id=\"cmd-results\"></div>\n </div>\n</div>\n\n<!-- Toast container -->\n<div class=\"toast-container\" id=\"toast-container\"></div>\n\n<!-- Log ticker -->\n<div class=\"log-ticker\">\n <div class=\"dot\"></div>\n <div class=\"scroll\" id=\"log-scroll\">Hive Mind initializing\u2026</div>\n <button class=\"icon-btn\" style=\"width:28px;height:24px;padding:0\" onclick=\"toggleLogPanel()\" title=\"Open log\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"/></svg></button>\n</div>\n\n<!-- Expandable log panel -->\n<div class=\"log-panel\" id=\"log-panel\">\n <div class=\"head\">\n <div class=\"title\">LIVE ACTIVITY</div>\n <div class=\"flex gap\">\n <button class=\"btn sm ghost\" onclick=\"clearLogs()\">Clear</button>\n <button class=\"btn sm ghost\" onclick=\"toggleLogPanel()\">Close</button>\n </div>\n </div>\n <div id=\"log-panel-list\"></div>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Scripts\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<script>\nconst FEATURES = [\n ['intelligence','Brain Map','Visual map of every memory, project and tool'],\n ['memory','Memories','Browse and search saved knowledge'],\n ['chat','Ask the Brain','Ask questions in plain language'],\n ['activity','Activity','Live feed of what the brain is doing'],\n ['agents','Connected Tools','Which AI tools are linked to the brain'],\n ['livebrain','Brain Health','Memory quality with tips to improve'],\n ['ww2br','Project Memory','Memory and status for one project'],\n ['graph','Live Network','Live animation of tools talking to the brain'],\n ['seeder','Add Knowledge','Research a topic and save it as verified knowledge'],\n ['garden','Memory Garden','Watch your memories grow over time'],\n ['sabb','Sub-Agents','Share the right context with helper agents'],\n ['causal','Decision Trails','Trace why a decision was made'],\n ['dream','Reflections','Idle-time review that finds patterns and lessons'],\n ['debate','Debates','Multiple AI viewpoints argue a tough decision'],\n ['premortem','Risk Check','See what went wrong last time before a big task'],\n ['branch','Branch Memory','Memory that follows your git branch'],\n ['forget','Memory Cleanup','Old memories fade, important ones stay'],\n ['collision','Conflicts','Catch two tools editing the same code'],\n ['reputation','Trust Scores','How reliable each AI tool has been'],\n ['calibration','Accuracy','How well confidence matches being right'],\n ['attention','Focus Map','Which memories shaped a decision'],\n ['formal','Code Rules','Turn plain rules into linter checks'],\n ['privacy','Privacy & Safety','Stay offline and block risky actions'],\n ['tokens','Cost & Usage','Track AI spend and find savings'],\n ['pr','Code Review','Generate a pull-request review'],\n ['voice','Voice','Talk to the brain hands-free'],\n ['team','Team Sync','Share memory with teammates'],\n ['exchange','Share Brain','Export or import memory packs'],\n ['features','Features','Turn individual features on or off'],\n ['models','AI Models','Choose which AI models the brain uses'],\n ['config','Settings','Set up and adjust Shadow Brain'],\n ['overview','Home','Your starting point and summary'],\n];\n\nlet ws = null;\nlet state = { hive: null, lastRefreshAt: 0 };\nlet charts = {};\n\n// \u2500\u2500\u2500\u2500 Tab switching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.nav-item').forEach(el => el.addEventListener('click', () => switchTab(el.dataset.tab, el.dataset.title)));\nfunction switchTab(id, title) {\n document.querySelectorAll('.nav-item').forEach(el => el.classList.toggle('active', el.dataset.tab === id));\n document.querySelectorAll('.section').forEach(el => el.classList.toggle('active', el.id === 'tab-' + id));\n if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');\n onTabOpen(id);\n animateSectionIn(id);\n}\n// Subtle staggered card entrance on real tab switches. Guarded so it no-ops\n// if GSAP's CDN failed to load. NOT called from the 5s refresh loop, so cards\n// don't re-fade on every poll.\nfunction animateSectionIn(id) {\n if (!window.gsap) return;\n const sec = document.getElementById('tab-' + id);\n if (!sec) return;\n const cards = sec.querySelectorAll('.card');\n if (!cards.length) return;\n try { window.gsap.from(cards, { opacity: 0, y: 12, duration: 0.34, stagger: 0.035, ease: 'power2.out', clearProps: 'opacity,transform' }); } catch (e) {}\n}\nfunction onTabOpen(tab) {\n const fn = { overview: refreshOverview, intelligence: refreshIntelligence, ww2br: refreshWW2BR, graph: startGraph, agents: refreshAgents, memory: () => { refreshMemoryIntelligence(); searchMemory(); }, seeder: refreshSeedDrafts, livebrain: refreshLiveBrain, chat: refreshChat, activity: refreshActivity,\n sabb: refreshSABB, causal: refreshCausal, collision: refreshCollision, dream: refreshDream, reputation: refreshReputation,\n tokens: refreshTokens, forget: refreshForget, formal: null, calibration: refreshCalibration, branch: refreshBranch,\n privacy: refreshPrivacy, garden: refreshGarden, team: refreshTeam, exchange: refreshExchange, features: refreshFeatures,\n models: refreshModels, config: refreshConfig }[tab];\n if (fn) fn();\n}\n\n// \u2500\u2500\u2500\u2500 Sidebar mobile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }\n\n// \u2500\u2500\u2500\u2500 Theme toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleTheme() {\n const html = document.documentElement;\n const newT = html.dataset.theme === 'light' ? 'dark' : 'light';\n html.dataset.theme = newT;\n try { localStorage.setItem('hive.theme', newT); } catch {}\n if (charts.tokens) updateChartColors();\n}\n(function initTheme(){\n try {\n const t = localStorage.getItem('hive.theme');\n // Default to light for better readability\n document.documentElement.dataset.theme = t || 'light';\n } catch { document.documentElement.dataset.theme = 'light'; }\n})();\n\n// \u2500\u2500\u2500\u2500 Simple / Advanced navigation mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction setMode(mode) {\n const m = mode === 'advanced' ? 'advanced' : 'simple';\n document.documentElement.dataset.navmode = m;\n try { localStorage.setItem('hive.navmode', m); } catch {}\n document.querySelectorAll('.mode-btn').forEach(b => b.classList.toggle('active', b.dataset.mode === m));\n // If we just hid the tab the user is on, gently return them to Home.\n if (m === 'simple') {\n const active = document.querySelector('.nav-item.active');\n if (active && active.closest('[data-adv]')) switchTab('overview', 'Home');\n }\n}\n(function initMode(){\n let m = 'simple';\n try { m = localStorage.getItem('hive.navmode') || 'simple'; } catch {}\n setMode(m);\n})();\n\n// \u2500\u2500\u2500\u2500 API helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function api(path, body) {\n try {\n const opts = body !== undefined ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : {};\n const res = await fetch(path, opts);\n if (!res.ok) { toast('API error', res.status + ' ' + path, 'error'); return null; }\n const ct = res.headers.get('content-type') || '';\n return ct.includes('application/json') ? await res.json() : await res.text();\n } catch (err) { toast('Network error', err.message, 'error'); return null; }\n}\nfunction htmlEscape(value) {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\nfunction fmtDate(value) {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? 'unknown' : d.toLocaleString();\n}\n// Reusable animated empty-state. cta = {label, onclick} or null.\n// title/msg are inserted as-is (allow pre-escaped entities) \u2014 author-controlled strings only.\nfunction emptyState(icon, title, msg, cta) {\n const icons = {\n shield: '<path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/>',\n check: '<polyline points=\"20 6 9 17 4 12\"/>',\n target: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/>',\n 'git-branch': '<line x1=\"6\" y1=\"3\" x2=\"6\" y2=\"15\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>',\n brain: '<circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/>',\n inbox: '<polyline points=\"22 12 16 12 14 15 10 15 8 12 2 12\"/><path d=\"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z\"/>',\n };\n const path = icons[icon] || icons.inbox;\n let btn = '';\n if (cta && cta.label) {\n const handler = cta.onclick ? ' onclick=\"' + cta.onclick + '\"' : '';\n btn = '<button class=\"btn primary es-cta\"' + handler + '>' + htmlEscape(cta.label) + '</button>';\n }\n return '<div class=\"empty-state\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' + path + '</svg><div class=\"es-title\">' + title + '</div><div class=\"es-msg\">' + msg + '</div>' + btn + '</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Toasts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toast(title, body, variant='success') {\n const c = document.getElementById('toast-container');\n const icons = { success:'<path d=\"M20 6 9 17l-5-5\"/>', warn:'<path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>', error:'<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>', info:'<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>' };\n const el = document.createElement('div');\n el.className = 'toast ' + variant;\n el.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${icons[variant] || icons.info}</svg><div><div class=\"title\">${title}</div>${body ? '<div class=\"body\">' + body + '</div>' : ''}</div>`;\n c.appendChild(el);\n setTimeout(() => { el.classList.add('removing'); setTimeout(() => el.remove(), 400); }, 3800);\n}\n\n// \u2500\u2500\u2500\u2500 Animated counters \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst _counterState = {};\nfunction animateNum(id, target) {\n const el = document.getElementById(id);\n if (!el) return;\n const start = parseFloat((el.textContent || '0').replace(/[^d.-]/g, '')) || 0;\n if (start === target) { el.textContent = target.toLocaleString(); return; }\n if (_counterState[id]) cancelAnimationFrame(_counterState[id]);\n const startTime = performance.now();\n const duration = 650;\n const step = now => {\n const t = Math.min(1, (now - startTime) / duration);\n const eased = 1 - Math.pow(1 - t, 3);\n const val = start + (target - start) * eased;\n el.textContent = Math.round(val).toLocaleString();\n if (t < 1) _counterState[id] = requestAnimationFrame(step);\n else { el.textContent = target.toLocaleString(); delete _counterState[id]; }\n };\n _counterState[id] = requestAnimationFrame(step);\n}\nfunction animateMoney(id, target) {\n const el = document.getElementById(id);\n if (!el) return;\n const start = parseFloat((el.textContent || '0').replace(/[^d.-]/g, '')) || 0;\n if (Math.abs(start - target) < 0.001) { el.textContent = '$' + target.toFixed(2); return; }\n if (_counterState[id]) cancelAnimationFrame(_counterState[id]);\n const startTime = performance.now();\n const duration = 650;\n const step = now => {\n const t = Math.min(1, (now - startTime) / duration);\n const eased = 1 - Math.pow(1 - t, 3);\n const val = start + (target - start) * eased;\n el.textContent = '$' + val.toFixed(2);\n if (t < 1) _counterState[id] = requestAnimationFrame(step);\n else { el.textContent = '$' + target.toFixed(2); delete _counterState[id]; }\n };\n _counterState[id] = requestAnimationFrame(step);\n}\n\n// \u2500\u2500\u2500\u2500 Main refresh \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refresh() {\n const hive = await api('/api/v6/hive-status');\n if (!hive) return;\n state.hive = hive;\n state.lastRefreshAt = Date.now();\n\n animateNum('stat-agents', hive.totalAgentsConnected ?? 0);\n animateNum('stat-memories', hive.totalMemoriesStored ?? 0);\n animateNum('stat-dreams', hive.modules.dream?.totalDreams ?? 0);\n animateNum('stat-tools', hive.totalAgentsConnected ?? 0);\n document.getElementById('badge-sabb').textContent = hive.modules.sabb?.totalSpawns ?? 0;\n document.getElementById('badge-causal').textContent = hive.modules.causal?.totalLinks ?? 0;\n document.getElementById('badge-collision').textContent = hive.modules.collision?.collisionsDetected ?? 0;\n document.getElementById('badge-dream').textContent = hive.modules.dream?.totalDreams ?? 0;\n const activeTab = document.querySelector('.nav-item.active')?.dataset.tab;\n if (activeTab) onTabOpen(activeTab);\n}\n\n// \u2500\u2500\u2500\u2500 Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshOverview() {\n const hive = state.hive || await api('/api/v6/hive-status');\n if (!hive) return;\n animateNum('ov-agents', hive.totalAgentsConnected ?? 0);\n animateNum('ov-memories', hive.totalMemoriesStored ?? 0);\n animateNum('ov-subagents', hive.modules.sabb?.totalSpawns ?? 0);\n document.getElementById('ov-subagents-sub').textContent = `grad: ${hive.modules.sabb?.graduated ?? 0} \u00B7 quar: ${hive.modules.sabb?.quarantined ?? 0}`;\n const rt = hive.runtime || {};\n document.getElementById('ov-current-agent').textContent = rt.currentAgentDisplay || 'Unknown';\n document.getElementById('ov-current-agent-sub').textContent = rt.provider ? (rt.provider + (rt.model ? ' / ' + rt.model : '')) : 'using the brain right now';\n loadConstHeroStats();\n\n const moduleRows = [\n ['SABB spawns', hive.modules.sabb?.totalSpawns ?? 0, 'good'],\n ['Causal links', hive.modules.causal?.totalLinks ?? 0, 'good'],\n ['Collisions detected', hive.modules.collision?.collisionsDetected ?? 0, 'warn'],\n ['Dreams recorded', hive.modules.dream?.totalDreams ?? 0, 'good'],\n ['Reputation receipts', hive.modules.reputation?.totalReceipts ?? 0, 'good'],\n ['Formal rules', hive.modules.formalBridge?.totalRules ?? 0, 'good'],\n ];\n document.getElementById('ov-modules').innerHTML = moduleRows.map(([k, v, cls]) =>\n `<div class=\"row\"><span class=\"k\">${k}</span><span class=\"v ${cls || ''}\">${v}</span></div>`\n ).join('');\n\n const health = [\n ['Local-first', hive.localFirst ? 'ON' : 'OFF', hive.localFirst ? 'good' : 'warn'],\n ['Current agent', hive.runtime?.currentAgentDisplay || 'Unknown', hive.runtime?.currentAgentTool === 'codex' ? 'good' : ''],\n ['Air-gap', hive.modules.airGap?.enabled ? 'ENABLED' : 'disabled', hive.modules.airGap?.enabled ? 'good' : ''],\n ['Version', hive.version, ''],\n ['Monthly cost', '$' + (hive.modules.tokenEconomy?.monthlyProjectionUsd?.toFixed?.(2) ?? '0.00'), 'warn'],\n ['Cost saved/mo', '$' + (hive.modules.tokenEconomy?.savingsOpportunitiesUsd?.toFixed?.(2) ?? '0.00'), 'good'],\n ];\n document.getElementById('ov-health').innerHTML = health.map(([k, v, cls]) => `<div class=\"row\"><span class=\"k\">${k}</span><span class=\"v ${cls}\">${v}</span></div>`).join('');\n\n // Tokens chart\n const tok = hive.modules.tokenEconomy;\n if (tok) drawTokensChart(tok.recentByAgent || tok.byAgent || {});\n\n // Timeline\n const timeline = await api('/api/v6/activity-log?limit=15') ?? [];\n document.getElementById('ov-timeline').innerHTML = timeline.length\n ? timeline.slice(0, 10).map(e => `<div style=\"padding:5px 0;border-bottom:1px solid var(--border);display:flex;gap:8px\"><span style=\"color:var(--text-dim);flex-shrink:0\">${new Date(e.timestamp).toLocaleTimeString()}</span><span class=\"pill ${typeToPill(e.type)}\">${e.type}</span><span style=\"color:var(--text-2);font-size:11px\">${(e.detail||'').slice(0,90)}</span></div>`).join('')\n : '<div class=\"empty\">No activity yet</div>';\n}\nfunction typeToPill(type) {\n if (type === 'memory-write') return 'green';\n if (type === 'subagent-spawn') return 'cyan';\n if (type === 'collision') return 'red';\n if (type === 'quarantine') return 'yellow';\n if (type === 'seed-draft') return 'yellow';\n if (type === 'memory-seeded') return 'green';\n if (['dream','revisit','counterfactual','consolidation','pattern-discovery','contradiction'].includes(type)) return 'purple';\n return '';\n}\n\nfunction getWW2ProjectPath() {\n return document.getElementById('ww2-project-path')?.value?.trim() || 'D:\\\\New Game';\n}\nasync function refreshWW2BR() {\n const projectDir = getWW2ProjectPath();\n const data = await api('/api/v6/ww2br-ops?projectDir=' + encodeURIComponent(projectDir));\n if (!data) return;\n\n const stats = data.stats || {};\n const hooked = (data.agents || []).filter(a => a.hookInstalled).length;\n document.getElementById('badge-ww2br').textContent = data.recent?.length ?? 0;\n animateNum('ww2-stat-memories', stats.totalEntries ?? 0);\n animateNum('ww2-stat-projects', stats.totalProjects ?? 0);\n animateNum('ww2-stat-hooks', hooked);\n document.getElementById('ww2-stat-hit').textContent = Math.round((stats.hitRate ?? 0) * 100) + '%';\n document.getElementById('ww2-stat-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';\n document.getElementById('ww2-stat-sync').textContent = stats.lastSync ? 'sync ' + fmtDate(stats.lastSync) : 'last sync unknown';\n\n const warnings = data.warnings || [];\n document.getElementById('ww2-warnings').innerHTML = warnings.length\n ? warnings.map(w => '<div class=\"warning-banner\"><strong>Warning</strong><span>' + htmlEscape(w) + '</span></div>').join('')\n : '';\n\n document.getElementById('ww2-timeline').innerHTML = renderMemoryItems(data.recent || [], 'No memories found for this workflow yet.');\n document.getElementById('ww2-agents').innerHTML = renderWW2Agents(data.agents || []);\n document.getElementById('ww2-categories').innerHTML = renderWW2Categories(data.categories || {});\n document.getElementById('ww2-quality').innerHTML = renderWW2Quality(data.quality);\n document.getElementById('ww2-resume-pack').innerHTML = renderWW2ResumePack(data.resumePack);\n\n const query = document.getElementById('ww2-query');\n if (query && !query.value) query.value = 'Gate 2 visual proof';\n if (!document.getElementById('ww2-recall-results').innerHTML.trim()) {\n document.getElementById('ww2-recall-results').innerHTML = '<div class=\"empty-tight\">Run a recall query to inspect relevant brain memory.</div>';\n }\n}\nfunction renderMemoryItems(items, emptyMessage) {\n if (!items || !items.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return items.map(item => {\n const importancePct = item.importance !== undefined ? Math.round((item.importance || 0) * 100) + '%' : '';\n // v7.0.0: real blended recall confidence (recency + importance + corroboration + usage + relevance).\n const conf = item.confidence && typeof item.confidence.score === 'number' ? item.confidence : null;\n const confCls = conf ? (conf.level === 'high' ? 'green' : conf.level === 'medium' ? 'yellow' : 'red') : '';\n const confBadge = conf ? '<span class=\"pill ' + confCls + '\" title=\"Heuristic recall score (not probability of truth) ' + Math.round(conf.score * 100) + '% (' + htmlEscape(conf.level) + ') | relevance ' + conf.signals.relevance + ', creation-age recency ' + conf.signals.recency + ', importance ' + conf.signals.importance + ', matching-content agent reports ' + conf.signals.corroboration + ', usage ' + conf.signals.usage + '\">\u2737 ' + Math.round(conf.score * 100) + '%</span>' : '';\n const sources = item.sources || (item.metadata && item.metadata.sources) || [];\n const tags = item.tags || (item.metadata && item.metadata.tags) || [];\n const quality = item.qualityScore !== undefined ? '<span class=\"pill\">Q ' + htmlEscape(item.qualityScore) + '</span>' : '';\n const sourceLine = sources.length\n ? '<div class=\"content\" style=\"font-size:10.5px;color:var(--text-3);margin-top:6px\">Sources: ' + sources.slice(0, 3).map(s => '<code>' + htmlEscape(s) + '</code>').join(' \u00B7 ') + '</div>'\n : '<div class=\"content\" style=\"font-size:10.5px;color:var(--warning);margin-top:6px\">Sources: none recorded</div>';\n const tagLine = tags.length ? '<span class=\"pill\">' + tags.slice(0, 3).map(htmlEscape).join(', ') + '</span>' : '';\n return '<div class=\"memory-item\">' +\n '<div class=\"meta\">' +\n '<span class=\"pill cyan\">' + htmlEscape(item.agentTool || item.agent || 'brain') + '</span>' +\n '<span class=\"pill\">' + htmlEscape(item.category || 'memory') + '</span>' +\n confBadge +\n (importancePct ? '<span class=\"pill\" title=\"Author-assigned importance\">imp ' + importancePct + '</span>' : '') +\n quality +\n tagLine +\n '<span>' + fmtDate(item.createdAt || item.lastAccessed) + '</span>' +\n '</div>' +\n '<div class=\"content\">' + htmlEscape(item.content || item.detail || '').slice(0, 420) + '</div>' +\n sourceLine +\n '</div>';\n }).join('');\n}\nfunction renderWW2Quality(quality) {\n if (!quality) return '<div class=\"empty-tight\">No memory quality data.</div>';\n const sourcePct = Math.round((quality.sourceCoverage || 0) * 100);\n const scoreCls = quality.avgScore >= 75 ? 'good' : quality.avgScore >= 55 ? 'warn' : 'bad';\n return [\n '<div class=\"row\"><span class=\"k\">Average score</span><span class=\"v ' + scoreCls + '\">' + htmlEscape(quality.avgScore ?? 0) + '/100</span></div>',\n '<div class=\"row\"><span class=\"k\">Source coverage</span><span class=\"v ' + (sourcePct >= 70 ? 'good' : 'warn') + '\">' + sourcePct + '%</span></div>',\n '<div class=\"row\"><span class=\"k\">Stale memories</span><span class=\"v ' + (quality.stale ? 'warn' : 'good') + '\">' + htmlEscape(quality.stale || 0) + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Duplicate clusters</span><span class=\"v ' + (quality.duplicates?.length ? 'warn' : 'good') + '\">' + htmlEscape(quality.duplicates?.length || 0) + '</span></div>',\n ].join('');\n}\nfunction renderWW2ResumePack(pack) {\n if (!pack) return '<div class=\"empty-tight\">No resume pack generated.</div>';\n const warnings = pack.warnings && pack.warnings.length\n ? '<div class=\"warning-stack\" style=\"margin:10px 0 0\">' + pack.warnings.slice(0, 3).map(w => '<div class=\"warning-banner\"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'\n : '';\n const blockers = pack.blockers && pack.blockers.length\n ? '<div class=\"brain-feed mt\">' + renderMemoryItems(pack.blockers.slice(0, 3), 'No blockers in pack.') + '</div>'\n : '<div class=\"empty-tight\">No blockers in pack.</div>';\n const stats = pack.stats || {};\n return '<div class=\"grid cols-3\">' +\n '<div class=\"row\"><span class=\"k\">Docs</span><span class=\"v\">' + htmlEscape(stats.docCount ?? 0) + '</span></div>' +\n '<div class=\"row\"><span class=\"k\">Memories</span><span class=\"v\">' + htmlEscape(stats.memoryCount ?? 0) + '</span></div>' +\n '<div class=\"row\"><span class=\"k\">Quality</span><span class=\"v\">' + htmlEscape(stats.avgQualityScore ?? 0) + '/100</span></div>' +\n '</div>' +\n '<div class=\"mt\" style=\"font-size:12px;color:var(--text-2)\">Generated ' + htmlEscape(fmtDate(pack.generatedAt)) + '. Uses visible docs and stored memories only.</div>' +\n warnings +\n '<div class=\"mt\">' + blockers + '</div>';\n}\nfunction openWW2ResumePack() {\n const url = '/api/v6/resume-pack?format=markdown&projectDir=' + encodeURIComponent(getWW2ProjectPath());\n window.open(url, '_blank', 'noopener');\n}\nfunction renderWW2Agents(agents) {\n if (!agents.length) return '<div class=\"empty-tight\">No known agent integrations returned.</div>';\n return agents.map(agent => {\n const cls = agent.hookInstalled ? 'green' : agent.detected ? 'cyan' : '';\n const hook = agent.hookInstalled ? 'hooked' : 'not wired';\n const detected = agent.detected ? 'detected' : 'not found';\n return '<div class=\"ops-agent\">' +\n '<div class=\"name\">' + htmlEscape(agent.displayName || agent.name) + '</div>' +\n '<div class=\"status\">' +\n '<span class=\"pill ' + cls + '\">' + hook + '</span>' +\n '<span class=\"pill\">' + detected + '</span>' +\n '</div>' +\n '</div>';\n }).join('');\n}\nfunction renderWW2Categories(categories) {\n const rows = Object.entries(categories).sort((a, b) => b[1] - a[1]);\n if (!rows.length) return '<div class=\"empty-tight\">No categories yet.</div>';\n return rows.map(([name, count]) => '<div class=\"row\"><span class=\"k\">' + htmlEscape(name) + '</span><span class=\"v\">' + count + '</span></div>').join('');\n}\nfunction setWW2Query(query) {\n document.getElementById('ww2-query').value = query;\n runWW2Recall();\n}\nasync function runWW2Recall() {\n const resultsEl = document.getElementById('ww2-recall-results');\n const query = document.getElementById('ww2-query').value.trim();\n const category = document.getElementById('ww2-category').value.trim();\n resultsEl.innerHTML = '<div class=\"empty-tight\">Searching brain memory...</div>';\n const data = await api('/api/v6/ww2br-recall', {\n projectDir: getWW2ProjectPath(),\n query,\n category: category || undefined,\n limit: 12,\n });\n if (!data) return;\n const prefix = data.projectScoped ? '' : '<div class=\"warning-banner\"><strong>Fallback</strong><span>No project-scoped matches; showing global brain recall results.</span></div>';\n resultsEl.innerHTML = prefix + renderMemoryItems(data.results || [], 'No recall matches for that query.');\n}\n\n// \u2500\u2500\u2500\u2500 Charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction chartTheme() {\n const dark = document.documentElement.dataset.theme !== 'light';\n return { grid: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', text: dark ? '#a4afc4' : '#3d4864' };\n}\nfunction spendLabels(buckets) {\n return Object.keys(buckets || {}).sort((a, b) => (buckets[b]?.spendUsd || 0) - (buckets[a]?.spendUsd || 0));\n}\nfunction drawSpendBarChart(canvasId, chartKey, buckets) {\n const ctx = document.getElementById(canvasId);\n if (!ctx) return;\n const labels = spendLabels(buckets);\n const data = labels.map(l => buckets[l].spendUsd || 0);\n const t = chartTheme();\n if (charts[chartKey]) charts[chartKey].destroy();\n charts[chartKey] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Spend (USD)', data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderRadius: 6 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text,callback:v=>'$'+Number(v).toFixed(2)}}} } });\n}\nfunction drawTokensChart(byAgent) {\n drawSpendBarChart('chart-tokens', 'tokens', byAgent || {});\n}\nfunction drawTokensAgentChart(byAgent) {\n drawSpendBarChart('chart-tokens-agent', 'tokensAgent', byAgent || {});\n}\nfunction drawTokensModelChart(byModel) {\n const ctx = document.getElementById('chart-tokens-model');\n if (!ctx) return;\n const labels = spendLabels(byModel);\n const data = labels.map(l => byModel[l].spendUsd || 0);\n const t = chartTheme();\n if (charts.tokensModel) charts.tokensModel.destroy();\n charts.tokensModel = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderWidth:0 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{color:t.text,boxWidth:12}}} } });\n}\nfunction renderSpendRows(buckets, emptyMessage) {\n const labels = spendLabels(buckets).slice(0, 8);\n if (!labels.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return labels.map(label => {\n const b = buckets[label] || {};\n const last = b.lastSeen ? new Date(b.lastSeen).toLocaleString() : '\u2014';\n return `<div class=\"row\"><span class=\"k\">${htmlEscape(label)} <span class=\"tag-code\">${b.calls || 0} calls</span></span><span class=\"v\">$${Number(b.spendUsd || 0).toFixed(4)} <span class=\"mono\" style=\"color:var(--text-3)\">${last}</span></span></div>`;\n }).join('');\n}\nfunction drawDreamChart(byType) {\n const ctx = document.getElementById('chart-dreams');\n if (!ctx) return;\n const labels = Object.keys(byType);\n const data = labels.map(l => byType[l]);\n const t = chartTheme();\n if (charts.dreams) charts.dreams.destroy();\n charts.dreams = new Chart(ctx, { type:'polarArea', data:{labels,datasets:[{data,backgroundColor:['#18ffff88','#a855f788','#ec489988','#10e68c88','#ffb53f88']}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:t.text}}},scales:{r:{grid:{color:t.grid},ticks:{color:t.text,backdropColor:'transparent'}}}} });\n}\nfunction drawForgetChart(states) {\n const ctx = document.getElementById('chart-forget');\n if (!ctx) return;\n const buckets = { strong: 0, medium: 0, weak: 0, fading: 0 };\n for (const s of states || []) {\n if (s.currentStrength >= 0.8) buckets.strong++;\n else if (s.currentStrength >= 0.5) buckets.medium++;\n else if (s.currentStrength >= 0.2) buckets.weak++;\n else buckets.fading++;\n }\n const t = chartTheme();\n if (charts.forget) charts.forget.destroy();\n charts.forget = new Chart(ctx, { type:'bar', data:{labels:Object.keys(buckets),datasets:[{data:Object.values(buckets),backgroundColor:['#10e68c','#18ffff','#ffb53f','#ff5864'],borderRadius:4}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text}}}} });\n}\nfunction updateChartColors() { for (const k of Object.keys(charts)) { charts[k].destroy(); delete charts[k]; } refresh(); }\n\n// \u2500\u2500\u2500\u2500 SABB \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshSABB() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.sabb ?? {};\n document.getElementById('sabb-stats').innerHTML = `\n <div class=\"row\"><span class=\"k\">Total spawns</span><span class=\"v\">${s.totalSpawns ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Quarantined</span><span class=\"v\">${s.quarantined ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Graduated</span><span class=\"v good\">${s.graduated ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Rejected</span><span class=\"v bad\">${s.rejected ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Avg sliver tokens</span><span class=\"v\">${Math.round(s.avgSliverTokens ?? 0)}</span></div>\n <div class=\"row\"><span class=\"k\">Avg graduation</span><span class=\"v\">${Math.round(s.avgGraduationMs ?? 0)}ms</span></div>`;\n const q = await api('/api/v6/subagent-quarantine') ?? [];\n document.getElementById('sabb-quarantine').innerHTML = q.length ? q.map(e =>\n `<tr><td><code>${e.id.slice(0,10)}</code></td><td><span class=\"pill purple\">${e.parentAgent}</span></td><td>${e.category}</td><td>${(e.confidence||0).toFixed(2)}</td><td>${(e.content||'').slice(0,100)}</td><td style=\"text-align:right\"><button class=\"btn sm success\" onclick=\"graduate('${e.id}')\">Graduate</button> <button class=\"btn sm danger\" onclick=\"rejectMem('${e.id}')\">Reject</button></td></tr>`\n ).join('') : '<tr><td colspan=\"6\"><div class=\"empty\">No quarantined memories</div></td></tr>';\n}\nasync function computeSliver() {\n const res = await api('/api/v6/subagent-sliver', {\n parentAgent: document.getElementById('sabb-parent').value,\n framework: document.getElementById('sabb-framework').value,\n taskDescription: document.getElementById('sabb-task').value,\n tokenBudget: parseInt(document.getElementById('sabb-budget').value || '300'),\n });\n if (res) { document.getElementById('sabb-sliver-output').textContent = res.markdown; toast('Sliver computed', res.memories.length + ' memories \u00B7 ' + res.tokenCount + ' tokens'); }\n}\nasync function graduate(id) { const r = await api('/api/v6/subagent-graduate', { memoryId: id }); if (r?.ok) { toast('Graduated', id.slice(0,10)); refreshSABB(); } }\nasync function rejectMem(id) { const r = await api('/api/v6/subagent-reject', { memoryId: id, reason: 'manual-reject' }); if (r?.ok) { toast('Rejected', id.slice(0,10), 'warn'); refreshSABB(); } }\n\n// \u2500\u2500\u2500\u2500 Causal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCausal() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.causal ?? {};\n document.getElementById('causal-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total links</span><span class=\"v\">${s.totalLinks ?? 0}</span></div><div class=\"row\"><span class=\"k\">Effects</span><span class=\"v\">${s.effects ?? 0}</span></div><div class=\"row\"><span class=\"k\">Causes</span><span class=\"v\">${s.causes ?? 0}</span></div>`;\n}\nasync function traceCausal() {\n const id = document.getElementById('causal-mem-id').value;\n if (!id) return toast('Missing', 'Enter a memory ID', 'warn');\n const res = await api('/api/v6/causal-trace', { memoryId: id, maxDepth: parseInt(document.getElementById('causal-depth').value || '8') });\n if (res) renderCausalDag(res, id);\n}\nasync function influenceCausal() {\n const id = document.getElementById('causal-mem-id').value;\n if (!id) return toast('Missing', 'Enter a memory ID', 'warn');\n const res = await api('/api/v6/causal-influence', { memoryId: id, maxDepth: 6 });\n if (res) renderCausalDag(res, id);\n}\nfunction renderCausalDag(chain, rootId) {\n const wrap = document.getElementById('causal-dag-wrap');\n const cv = document.getElementById('causal-dag');\n const empty = document.getElementById('causal-dag-empty');\n if (!cv || !wrap) return;\n const nodes = (chain && chain.nodes) || [];\n if (!nodes.length) { if (empty) { empty.style.display = 'flex'; empty.textContent = 'No causal links recorded for this memory yet.'; } return; }\n if (empty) empty.style.display = 'none';\n cv.width = wrap.clientWidth || 800; cv.height = wrap.clientHeight || 420;\n const W = cv.width, H = cv.height, ctx = cv.getContext('2d');\n // Layer nodes by depth\n const byDepth = {};\n let maxDepth = 0;\n for (const n of nodes) { const d = n.depth || 0; (byDepth[d] = byDepth[d] || []).push(n); if (d > maxDepth) maxDepth = d; }\n const pos = new Map();\n const layers = maxDepth + 1;\n for (let d = 0; d <= maxDepth; d++) {\n const row = byDepth[d] || [];\n const y = layers === 1 ? H / 2 : 40 + (d / Math.max(1, maxDepth)) * (H - 80);\n row.forEach((n, i) => { pos.set(n.id, { x: (i + 1) / (row.length + 1) * W, y: y, n: n }); });\n }\n ctx.clearRect(0, 0, W, H);\n // Links\n for (const l of (chain.links || [])) {\n const a = pos.get(l.causeId), b = pos.get(l.effectId);\n if (!a || !b) continue;\n ctx.strokeStyle = 'rgba(24,255,255,' + (0.25 + 0.5 * (l.strength || 0.5)) + ')';\n ctx.lineWidth = 1 + (l.strength || 0.5) * 2;\n ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();\n // arrowhead\n const ang = Math.atan2(b.y - a.y, b.x - a.x);\n ctx.fillStyle = 'rgba(24,255,255,0.7)';\n ctx.beginPath(); ctx.moveTo(b.x - 11 * Math.cos(ang - 0.4), b.y - 11 * Math.sin(ang - 0.4)); ctx.lineTo(b.x, b.y); ctx.lineTo(b.x - 11 * Math.cos(ang + 0.4), b.y - 11 * Math.sin(ang + 0.4)); ctx.fill();\n }\n // Nodes\n for (const p of pos.values()) {\n const isRoot = p.n.id === rootId;\n const col = isRoot ? '#ec4899' : '#a855f7';\n const r = isRoot ? 13 : 9;\n const halo = ctx.createRadialGradient(p.x, p.y, r * 0.4, p.x, p.y, r * 2.4);\n halo.addColorStop(0, col + '88'); halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(p.x, p.y, r * 2.4, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.stroke();\n ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';\n ctx.font = '10px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n const lbl = String(p.n.content || p.n.category || p.n.id).slice(0, 22);\n ctx.fillText(lbl, p.x, p.y + r + 10);\n }\n toast('Causal chain', nodes.length + ' nodes \u00B7 ' + ((chain.links || []).length) + ' links');\n}\nasync function linkCausal() {\n const r = await api('/api/v6/causal-link', {\n effectId: document.getElementById('causal-effect').value,\n causeId: document.getElementById('causal-cause').value,\n rationale: document.getElementById('causal-rationale').value,\n });\n if (r) { toast('Link recorded'); refreshCausal(); }\n}\n\n// \u2500\u2500\u2500\u2500 Collision \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCollision() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.collision ?? {};\n document.getElementById('collision-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Active intents</span><span class=\"v\">${s.activeIntents ?? 0}</span></div><div class=\"row\"><span class=\"k\">Collisions detected</span><span class=\"v warn\">${s.collisionsDetected ?? 0}</span></div><div class=\"row\"><span class=\"k\">Resolved</span><span class=\"v good\">${s.collisionsResolved ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg overlap</span><span class=\"v\">${(s.avgOverlapLines ?? 0).toFixed(1)} lines</span></div>`;\n const data = await api('/api/v6/collision-list') ?? { intents: [], alerts: [] };\n document.getElementById('collision-alerts').innerHTML = data.alerts.length\n ? data.alerts.map(a => `<div style=\"padding:10px;background:var(--bg-elev-2);border-left:3px solid var(--danger);border-radius:6px;margin-bottom:6px\"><div style=\"font-weight:600;font-size:12px\">${(a.filePath||'').split(/[\\\\/]/).pop()}</div><div class=\"mono\" style=\"margin-top:4px;color:var(--text-2)\">${a.suggestedResolution}</div></div>`).join('')\n : '<div class=\"empty\">No active alerts</div>';\n document.getElementById('collision-intents').innerHTML = data.intents.length ? data.intents.map(i =>\n `<tr><td><span class=\"pill cyan\">${i.agentTool}</span></td><td><code>${(i.filePath||'').split(/[\\\\/]/).pop()}</code></td><td>${i.startLine}-${i.endLine}</td><td>${(i.intent||'').slice(0,80)}</td><td class=\"mono\">${new Date(i.expiresAt).toLocaleTimeString()}</td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No active intents</div></td></tr>';\n}\n\n// \u2500\u2500\u2500\u2500 Dream \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshDream() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.dream ?? {};\n document.getElementById('dream-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total</span><span class=\"v\">${s.totalDreams ?? 0}</span></div><div class=\"row\"><span class=\"k\">Actionable</span><span class=\"v good\">${s.actionableCount ?? 0}</span></div><div class=\"row\"><span class=\"k\">Acknowledged</span><span class=\"v\">${s.acknowledgedCount ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg confidence</span><span class=\"v\">${((s.avgConfidence ?? 0) * 100).toFixed(0)}%</span></div>`;\n if (s.byType) drawDreamChart(s.byType);\n const dreams = await api('/api/v6/dream-list') ?? [];\n document.getElementById('dream-list').innerHTML = dreams.length ? dreams.map(d =>\n `<tr><td><span class=\"pill purple\">${d.type}</span></td><td>${(d.content||'').slice(0,160)}</td><td>${((d.confidence||0)*100).toFixed(0)}%</td><td>${d.actOnNextSession ? '<span class=\"pill green\">yes</span>' : '\u2014'}</td><td><button class=\"btn sm\" onclick=\"ackDream('${d.id}')\">Ack</button></td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No dreams yet. Run a cycle.</div></td></tr>';\n}\nasync function runDream() { const d = await api('/api/v6/dream-run', {}); if (d) { toast('Dreams generated', (d.length || 0) + ' new'); refreshDream(); } }\nasync function startDreamLoop() { await api('/api/v6/dream-start', {}); toast('Dream loop', 'Background reflection active'); }\nasync function ackDream(id) { await api('/api/v6/dream-ack', { id }); refreshDream(); }\n\n// \u2500\u2500\u2500\u2500 Reputation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshReputation() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.reputation ?? {};\n document.getElementById('reputation-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total agents</span><span class=\"v\">${s.totalAgents ?? 0}</span></div><div class=\"row\"><span class=\"k\">Total receipts</span><span class=\"v\">${s.totalReceipts ?? 0}</span></div><div class=\"row\"><span class=\"k\">Verified</span><span class=\"v good\">${s.verifiedReceipts ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg accuracy</span><span class=\"v\">${((s.averageAccuracy ?? 0) * 100).toFixed(1)}%</span></div>`;\n const top = s.topAgents ?? [];\n document.getElementById('rep-leaderboard').innerHTML = top.length ? top.map(a =>\n `<tr><td><span class=\"pill cyan\">${a.agentTool}</span></td><td>${((a.accuracy ?? 0) * 100).toFixed(1)}%</td><td>${a.decisions}</td><td class=\"mono\">\u2014</td></tr>`\n ).join('') : '<tr><td colspan=\"4\"><div class=\"empty\">No signed decisions yet</div></td></tr>';\n const grid = document.getElementById('rep-trust-grid');\n if (grid) {\n if (!top.length) {\n grid.innerHTML = emptyState('shield', 'No trust receipts yet', 'Agents build a portable trust score by signing decision receipts. Run: shadow-brain reputation sign --agent <name>', null);\n } else {\n grid.innerHTML = top.map(function (a, i) {\n return '<div class=\"trust-card\"><div class=\"trust-ring\"><canvas id=\"trust-ring-' + i + '\"></canvas><div class=\"tr-pct\">' + Math.round((a.accuracy || 0) * 100) + '</div></div><div class=\"tc-name\">' + htmlEscape(a.agentTool) + '</div><div class=\"tc-sub\">' + a.decisions + ' decisions</div></div>';\n }).join('');\n top.forEach(function (a, i) { drawTrustRing('trust-ring-' + i, a.accuracy || 0); });\n }\n }\n}\nfunction drawTrustRing(id, frac) {\n const cv = document.getElementById(id); if (!cv || !cv.getContext) return;\n cv.width = 92; cv.height = 92;\n const ctx = cv.getContext('2d'), cx = 46, cy = 46, r = 38;\n ctx.clearRect(0, 0, 92, 92);\n ctx.lineWidth = 8; ctx.strokeStyle = 'rgba(120,140,180,0.18)';\n ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();\n const col = frac >= 0.8 ? '#10e68c' : frac >= 0.5 ? '#ffb53f' : '#ff5864';\n ctx.strokeStyle = col; ctx.lineCap = 'round';\n ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + frac * Math.PI * 2); ctx.stroke();\n}\nasync function genBadge() {\n const res = await api('/api/v6/reputation-badge', { agentTool: document.getElementById('rep-agent').value, agentVersion: document.getElementById('rep-ver').value || undefined });\n document.getElementById('rep-badge-output').textContent = res?.badge || 'No reputation data yet. Sign a decision first via CLI: shadow-brain reputation sign --agent ... --ver ...';\n}\n\n// \u2500\u2500\u2500\u2500 Debate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runDebate() {\n const res = await api('/api/v6/debate-run', {\n question: document.getElementById('debate-question').value,\n context: document.getElementById('debate-context').value,\n turns: parseInt(document.getElementById('debate-turns').value || '2'),\n });\n if (res) {\n document.getElementById('debate-output').textContent = res.turns.map(t => '[' + t.position + '] ' + t.statement).join('\\n\\n') + '\\n\\nVERDICT: ' + res.verdict;\n toast('Debate complete', res.durationMs + 'ms');\n }\n}\n\n// \u2500\u2500\u2500\u2500 Pre-mortem \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runPremortem() {\n const task = document.getElementById('pm-task').value;\n if (!task.trim()) return toast('Missing', 'Describe the task first', 'warn');\n const res = await api('/api/v6/premortem-run', { taskDescription: task, projectDir: '' });\n if (!res) return;\n const out = document.getElementById('pm-output');\n const sevColor = { critical: '#ff5864', high: '#ffb53f', medium: '#ffca28', low: '#10e68c' };\n const riskCol = res.riskScore > 0.6 ? '#ff5864' : res.riskScore > 0.3 ? '#ffb53f' : '#10e68c';\n const failures = res.failures || [];\n // Risk matrix buckets: rows = severity (crit..low), cols = probability quartile\n const sevOrder = ['critical', 'high', 'medium', 'low'];\n const cells = {};\n for (const f of failures) {\n const col = Math.min(3, Math.floor((f.probability || 0) * 4));\n const key = (f.severity || 'low') + ':' + col;\n cells[key] = (cells[key] || 0) + 1;\n }\n const cellColor = (sev, col) => {\n const sevW = { critical: 3, high: 2, medium: 1, low: 0 }[sev];\n const risk = (sevW + col) / 6;\n return risk > 0.6 ? 'rgba(255,88,100,0.85)' : risk > 0.35 ? 'rgba(255,181,63,0.8)' : 'rgba(16,230,140,0.7)';\n };\n let matrix = '<div class=\"risk-matrix\"><div class=\"rm-h\"></div><div class=\"rm-h\">P 0-25%</div><div class=\"rm-h\">25-50%</div><div class=\"rm-h\">50-75%</div><div class=\"rm-h\">75-100%</div>';\n for (const sev of sevOrder) {\n matrix += '<div class=\"rm-yl\">' + sev + '</div>';\n for (let col = 0; col < 4; col++) {\n const c = cells[sev + ':' + col] || 0;\n matrix += c ? '<div class=\"rm-cell\" style=\"background:' + cellColor(sev, col) + '\">' + c + '</div>' : '<div class=\"rm-cell empty\">\u00B7</div>';\n }\n }\n matrix += '</div>';\n let html = '<div class=\"risk-head\"><div class=\"risk-gauge\"><canvas id=\"pm-gauge\"></canvas><div class=\"rg-num\" style=\"color:' + riskCol + '\">' + Math.round(res.riskScore * 100) + '</div></div>';\n html += '<div><div style=\"font-size:13px;font-weight:700;margin-bottom:4px\">Overall Risk Score</div><div style=\"font-size:12px;color:var(--text-2);max-width:520px;line-height:1.5\">' + htmlEscape(res.summary || '') + '</div></div></div>';\n html += matrix;\n html += failures.map(function (f) {\n const col = sevColor[f.severity] || '#10e68c';\n return '<div class=\"fail-card\" style=\"border-left-color:' + col + '\"><div class=\"fc-head\"><span class=\"pill\" style=\"color:' + col + ';border-color:' + col + '\">' + htmlEscape(f.severity) + '</span><span class=\"pill\">p=' + (f.probability || 0).toFixed(2) + '</span><span class=\"pill\">' + htmlEscape(f.source || '') + '</span></div><div class=\"fc-desc\">' + htmlEscape(f.description || '') + '</div>' + (f.mitigation ? '<div class=\"fc-mit\">' + htmlEscape(f.mitigation) + '</div>' : '') + '</div>';\n }).join('') || emptyState('check', 'No predicted failures', 'The pre-mortem surfaced no significant risks for this task based on past incidents and similar projects.', null);\n out.innerHTML = html;\n drawRiskGauge('pm-gauge', res.riskScore, riskCol);\n}\nfunction drawRiskGauge(id, frac, col) {\n const cv = document.getElementById(id); if (!cv || !cv.getContext) return;\n cv.width = 96; cv.height = 96;\n const ctx = cv.getContext('2d'), cx = 48, cy = 48, r = 38;\n ctx.clearRect(0, 0, 96, 96);\n ctx.lineWidth = 9; ctx.strokeStyle = 'rgba(120,140,180,0.18)';\n ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();\n ctx.strokeStyle = col; ctx.lineCap = 'round';\n ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.max(0.02, frac) * Math.PI * 2); ctx.stroke();\n}\n\n// \u2500\u2500\u2500\u2500 Attention \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runAttention() {\n const ids = document.getElementById('att-memories').value.split(',').map(s => s.trim()).filter(Boolean);\n const res = await api('/api/v6/attention-heatmap', { decisionText: document.getElementById('att-decision').value, candidateMemoryIds: ids });\n const out = document.getElementById('att-output');\n if (!res) return;\n const weights = res.weights || [];\n if (!weights.length) { out.innerHTML = emptyState('target', 'No attention weights', 'Provide a decision and some candidate memory IDs (or let it use recent memories) to see which ones carried the most weight.', null); return; }\n out.innerHTML = '<div style=\"font-size:11.5px;color:var(--text-3);margin-bottom:10px\">' + res.totalMemoriesConsidered + ' memories considered for: <strong style=\"color:var(--text-2)\">' + htmlEscape(res.decisionText || '') + '</strong></div>' + weights.map(function (w) {\n const pct = (w.weight || 0) * 100;\n const col = pct > 60 ? '#ff5864' : pct > 30 ? '#ffb53f' : '#18ffff';\n return '<div class=\"heat-row\"><div class=\"hr-top\"><span>' + htmlEscape((w.memoryContent || '').slice(0, 80)) + '</span><span class=\"hr-pct\" style=\"color:' + col + '\">' + pct.toFixed(1) + '%</span></div><div class=\"hr-track\"><div class=\"hr-fill\" style=\"width:' + Math.max(2, pct) + '%;background:' + col + '\"></div></div>' + (w.reasoning ? '<div class=\"hr-reason\">' + htmlEscape(w.reasoning) + '</div>' : '') + '</div>';\n }).join('');\n}\n\n// \u2500\u2500\u2500\u2500 Tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshTokens() {\n const res = await api('/api/v6/tokens-report');\n if (!res) return;\n const runtime = res.runtime || {};\n document.getElementById('tok-agent').textContent = runtime.currentAgentDisplay || 'Unknown';\n document.getElementById('tok-runtime').textContent = (runtime.provider || 'provider') + (runtime.model ? ' / ' + runtime.model : '');\n document.getElementById('tok-runtime-note').innerHTML = `<strong>Accuracy</strong><span>${htmlEscape(runtime.note || 'Token records are historical. Current agent and model spend are shown separately.')}</span>`;\n document.getElementById('tok-total').textContent = '$' + (res.totalSpendUsd || 0).toFixed(4);\n document.getElementById('tok-recent').textContent = '$' + (res.recentSpendUsd || 0).toFixed(4);\n document.getElementById('tok-proj').textContent = '$' + (res.monthlyProjectionUsd || 0).toFixed(2);\n document.getElementById('tok-save').textContent = '$' + (res.savingsOpportunitiesUsd || 0).toFixed(2);\n const calls = Object.values(res.byAgent || {}).reduce((a, b) => a + b.calls, 0);\n document.getElementById('tok-calls').textContent = calls;\n document.getElementById('tok-calls-sub').textContent = `${(res.totalInputTokens || 0).toLocaleString()} in \u00B7 ${(res.totalOutputTokens || 0).toLocaleString()} out`;\n drawTokensAgentChart(res.recentByAgent || res.byAgent || {});\n drawTokensModelChart(res.byModel || {});\n document.getElementById('tok-agent-table').innerHTML = renderSpendRows(res.recentByAgent || res.byAgent || {}, 'No recent agent spend records.');\n document.getElementById('tok-model-table').innerHTML = renderSpendRows(res.byModel || {}, 'No model spend records.');\n document.getElementById('tok-suggestions').innerHTML = (res.suggestions || []).map(s => `<div style=\"padding:8px 0;border-bottom:1px solid var(--border);display:flex;gap:8px\"><span style=\"color:var(--success);flex-shrink:0\">\uD83D\uDCA1</span><span style=\"font-size:12px\">${s}</span></div>`).join('') || '<div class=\"empty\">Running lean \u2014 no suggestions</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Forget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshForget() {\n drawForgetChart([]);\n}\nasync function runForget() {\n const r = await api('/api/v6/forget-consolidate', {});\n if (!r) return;\n document.getElementById('forget-output').innerHTML = `\n <div class=\"row\"><span class=\"k\">Cycle</span><span class=\"v\">${r.cycle}</span></div>\n <div class=\"row\"><span class=\"k\">Promoted</span><span class=\"v good\">${r.promoted}</span></div>\n <div class=\"row\"><span class=\"k\">Demoted</span><span class=\"v\">${r.demoted}</span></div>\n <div class=\"row\"><span class=\"k\">Forgotten</span><span class=\"v bad\">${r.forgotten}</span></div>\n <div class=\"row\"><span class=\"k\">Strengthened</span><span class=\"v good\">${r.strengthened}</span></div>\n <div class=\"row\"><span class=\"k\">Duration</span><span class=\"v\">${r.durationMs}ms</span></div>`;\n toast('Consolidation cycle', 'cycle #' + r.cycle);\n}\n\n// \u2500\u2500\u2500\u2500 Formal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function genFormal() {\n const res = await api('/api/v6/formal-generate', { text: document.getElementById('formal-text').value });\n if (res) { document.getElementById('formal-output').textContent = JSON.stringify(res, null, 2); toast('Rule generated'); }\n}\nasync function exportEslint() {\n const res = await fetch('/api/v6/formal-export-eslint').then(r => r.text());\n document.getElementById('formal-export').textContent = res;\n}\nasync function exportSemgrep() {\n const res = await fetch('/api/v6/formal-export-semgrep').then(r => r.text());\n document.getElementById('formal-export').textContent = res;\n}\n\n// \u2500\u2500\u2500\u2500 Calibration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCalibration() {\n const scores = await api('/api/v6/calibration-scores') ?? [];\n document.getElementById('calibration-table').innerHTML = scores.length ? scores.map(s =>\n `<tr><td><span class=\"pill cyan\">${s.agentTool}</span></td><td>${s.category}</td><td>${s.brierScore}</td><td>${s.calibrationError}</td><td>${(s.overconfidenceRatio * 100).toFixed(0)}%</td><td class=\"v ${s.trustWeight > 0.8 ? 'good' : s.trustWeight > 0.5 ? '' : 'warn'}\">${s.trustWeight}</td><td>${s.sampleSize}</td></tr>`\n ).join('') : '<tr><td colspan=\"7\"><div class=\"empty\">No calibration data yet</div></td></tr>';\n drawCalibrationChart(scores);\n}\nfunction drawCalibrationChart(scores) {\n const cv = document.getElementById('chart-calibration'); if (!cv || !window.Chart) return;\n const t = chartTheme();\n if (charts.calibration) charts.calibration.destroy();\n const rows = (scores || []).slice(0, 8);\n const labels = rows.map(function (s) { return s.agentTool + '/' + String(s.category).slice(0, 10); });\n charts.calibration = new Chart(cv, {\n type: 'bar',\n data: { labels: labels, datasets: [\n { label: 'Brier (lower better)', data: rows.map(function (s) { return s.brierScore; }), backgroundColor: '#ff5864', borderRadius: 4 },\n { label: 'Trust weight', data: rows.map(function (s) { return s.trustWeight; }), backgroundColor: '#10e68c', borderRadius: 4 },\n ] },\n options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: t.text } } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { beginAtZero: true, max: 1, grid: { color: t.grid }, ticks: { color: t.text } } } },\n });\n}\n\n// \u2500\u2500\u2500\u2500 Branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshBranch() {\n const r = await api('/api/v6/branch-state');\n const cards = document.getElementById('branch-cards');\n const cats = document.getElementById('branch-cats');\n if (!r) { if (cards) cards.innerHTML = emptyState('git-branch', 'No branch data', 'Branch-aware memory activates inside a git repository. Open a project with branches to see scoped memory here.', null); return; }\n if (cards) cards.innerHTML =\n '<div class=\"card stat-card accent\"><div class=\"label\">Current Branch</div><div class=\"value\" style=\"font-size:20px\" id=\"br-cur\">' + htmlEscape(r.currentBranch || '\u2014') + '</div><div class=\"trend\">active context</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Branch Memories</div><div class=\"value\">' + (r.branchMemoryCount ?? 0) + '</div><div class=\"trend\">scoped to branch</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Global Memories</div><div class=\"value\">' + (r.globalMemoryCount ?? 0) + '</div><div class=\"trend\">all branches</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Active IDs</div><div class=\"value\">' + ((r.activeMemoryIds || []).length) + '</div><div class=\"trend\">in working set</div></div>';\n if (cats) {\n const list = r.branchSpecificCategories || [];\n cats.innerHTML = list.length ? list.map(function (c) { return '<span class=\"tc\">' + htmlEscape(String(c)) + '</span>'; }).join('') : '<div class=\"empty-tight\">No branch-specific categories.</div>';\n }\n}\n\n// \u2500\u2500\u2500\u2500 Privacy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshPrivacy() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const a = hive?.modules.airGap ?? {};\n document.getElementById('airgap-state').innerHTML = `\n <div class=\"row\"><span class=\"k\">Enabled</span><span class=\"v ${a.enabled ? 'good' : ''}\">${a.enabled ? 'YES' : 'no'}</span></div>\n <div class=\"row\"><span class=\"k\">Policy</span><span class=\"v\">${a.policy || '\u2014'}</span></div>\n <div class=\"row\"><span class=\"k\">Blocked outbound</span><span class=\"v bad\">${a.blockedOutboundCount ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Allowed local</span><span class=\"v good\">${a.allowedLocalCount ?? 0}</span></div>`;\n const qlist = await api('/api/v6/quarantine-list?pendingOnly=true') ?? [];\n document.getElementById('quarantine-state').innerHTML = `<div class=\"row\"><span class=\"k\">Pending</span><span class=\"v\">${qlist.length}</span></div><div class=\"row\"><span class=\"k\">Auto-delete after</span><span class=\"v\">7 days</span></div>`;\n document.getElementById('quarantine-list').innerHTML = qlist.length ? qlist.map(e =>\n `<tr><td><code>${e.id.slice(0,10)}</code></td><td><span class=\"pill\">${e.source}</span></td><td>${(e.claim||'').slice(0,100)}</td><td class=\"mono\">${e.reasonFlagged}</td><td style=\"text-align:right\"><button class=\"btn sm success\">Promote</button> <button class=\"btn sm danger\">Reject</button></td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No quarantined claims</div></td></tr>';\n}\nasync function enableAirGap() { await api('/api/v6/airgap-enable', { policy: 'strict' }); toast('Air-gap enabled','strict mode'); refresh(); refreshPrivacy(); }\nasync function disableAirGap() { await api('/api/v6/airgap-disable', {}); toast('Air-gap disabled','', 'warn'); refresh(); refreshPrivacy(); }\nasync function toggleAirGap() { const hive = state.hive || await api('/api/v6/hive-status'); if (hive?.modules.airGap?.enabled) disableAirGap(); else enableAirGap(); }\n\n// \u2500\u2500\u2500\u2500 Voice \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function sendVoice() {\n const r = await api('/api/v6/voice-process', { transcript: document.getElementById('voice-input').value });\n if (r) document.getElementById('voice-output').textContent = `Intent: ${r.intent}\\n\\n${r.response}`;\n}\n\n// \u2500\u2500\u2500\u2500 Garden \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshGarden() {\n const snap = await api('/api/v6/garden-snapshot?limit=200') ?? [];\n const stats = await api('/api/v6/garden-stats') ?? { nodes: 0, avgBloom: 0, linkedFraction: 0 };\n document.getElementById('garden-stats').textContent = `Nodes: ${stats.nodes} \u00B7 avg bloom: ${stats.avgBloom} \u00B7 linked: ${Math.round((stats.linkedFraction||0) * 100)}%`;\n drawGarden(snap);\n}\nfunction drawGarden(nodes) {\n const c = document.getElementById('garden-canvas');\n if (!c) return;\n const ctx = c.getContext('2d'); const W = c.width, H = c.height;\n ctx.clearRect(0, 0, W, H);\n if (!nodes.length) { ctx.fillStyle = '#556080'; ctx.font = '14px \"JetBrains Mono\", monospace'; ctx.textAlign = 'center'; ctx.fillText('No garden yet. Let the brain grow.', W/2, H/2); return; }\n const pos = nodes.map((n, i) => {\n const a = i / nodes.length * Math.PI * 2;\n const r = 110 + (1 - n.bloom) * 170 + Math.sin(i * 1.3) * 30;\n return { x: W/2 + Math.cos(a) * r, y: H/2 + Math.sin(a) * r * 0.75, node: n };\n });\n ctx.strokeStyle = 'rgba(24,255,255,0.1)'; ctx.lineWidth = 1;\n for (const p of pos) for (const c of p.node.connections) { const t = pos.find(x => x.node.id === c); if (t) { ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(t.x, t.y); ctx.stroke(); } }\n for (const p of pos) {\n const col = p.node.kind === 'decision' ? '#a855f7' : p.node.kind === 'pattern' ? '#18ffff' : '#10e68c';\n const rad = 4 + p.node.bloom * 9;\n const grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, rad * 2.5);\n grd.addColorStop(0, col + 'cc'); grd.addColorStop(1, col + '00');\n ctx.fillStyle = grd; ctx.beginPath(); ctx.arc(p.x, p.y, rad * 2.5, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = col; ctx.globalAlpha = Math.max(0.4, p.node.bloom);\n ctx.beginPath(); ctx.arc(p.x, p.y, rad, 0, Math.PI * 2); ctx.fill();\n ctx.globalAlpha = 1;\n }\n}\n\n// \u2500\u2500\u2500\u2500 PR Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function genPRReview() {\n const r = await api('/api/v6/pr-review-generate', {\n repo: document.getElementById('pr-repo').value,\n prNumber: parseInt(document.getElementById('pr-num').value || '0'),\n changedFiles: document.getElementById('pr-files').value.split(',').map(s => s.trim()).filter(Boolean),\n diffSummary: document.getElementById('pr-diff').value,\n });\n if (r) { document.getElementById('pr-output').textContent = r.body; toast('Review generated'); }\n}\n\n// \u2500\u2500\u2500\u2500 Team \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshTeam() {\n const self = await api('/api/v6/team-self') ?? {};\n const peers = await api('/api/v6/team-peers') ?? [];\n document.getElementById('team-self').innerHTML = `<div class=\"row\"><span class=\"k\">Peer ID</span><span class=\"v mono\">${self.peerId || '\u2014'}</span></div><div class=\"row\"><span class=\"k\">Display name</span><span class=\"v\">${self.displayName || '\u2014'}</span></div>`;\n document.getElementById('team-peers').innerHTML = peers.length ? peers.map(p => `<div class=\"row\"><span class=\"k\">${p.displayName}</span><span class=\"v mono\">${p.peerId}</span></div>`).join('') : '<div class=\"empty\">No peers connected. Connect via WebRTC.</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Exchange \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshExchange() {\n const packages = await api('/api/v6/exchange-list') ?? [];\n document.getElementById('ex-list').innerHTML = packages.length ? packages.map(p => `<div class=\"row\"><span class=\"k\"><code>${p.name}</code><br/><span style=\"font-size:10px;color:var(--text-3)\">by ${p.author}</span></span><span class=\"v\">${p.memoryCount} memories</span></div>`).join('') : '<div class=\"empty\">No local packages yet</div>';\n}\nasync function exportPackage() {\n const res = await api('/api/v6/exchange-export', {\n name: document.getElementById('ex-name').value || 'untitled',\n description: document.getElementById('ex-desc').value,\n author: document.getElementById('ex-author').value,\n tags: document.getElementById('ex-tags').value.split(',').map(s => s.trim()).filter(Boolean),\n });\n if (res) { document.getElementById('ex-out').textContent = 'Saved \u2192 ' + res.filePath; toast('Package exported'); refreshExchange(); }\n}\n\n// \u2500\u2500\u2500\u2500 Features \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshFeatures() {\n const cfg = (await api('/api/v6/features-config')) ?? {};\n document.getElementById('feature-grid').innerHTML = FEATURES.map(([id, name, desc]) => {\n const on = cfg[id] !== false; // default enabled\n return `<div class=\"feature-toggle\" id=\"ft-${id}\">\n <div class=\"icon\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/></svg></div>\n <div class=\"meta\"><div class=\"name\">${name}</div><div class=\"desc\">${desc}</div></div>\n <label class=\"switch\"><input type=\"checkbox\" ${on ? 'checked' : ''} data-feature=\"${id}\"><span class=\"slider\"></span></label>\n </div>`;\n }).join('');\n // Bind listeners cleanly so the toggle always fires\n document.querySelectorAll('#feature-grid input[data-feature]').forEach(el => {\n el.addEventListener('change', async e => {\n const id = e.target.getAttribute('data-feature');\n const on = e.target.checked;\n const res = await api('/api/v6/features-config', { [id]: on });\n toast(on ? 'Enabled' : 'Disabled', id, on ? 'success' : 'warn');\n if (!res) { e.target.checked = !on; /* revert on error */ }\n });\n });\n}\nasync function toggleFeature(id, on) {\n const res = await api('/api/v6/features-config', { [id]: on });\n if (res) toast(on ? 'Enabled' : 'Disabled', id, on ? 'success' : 'warn');\n}\n\n// \u2500\u2500\u2500\u2500 Models & Intelligence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _providers = [];\nlet _allModels = {};\nasync function refreshModels() {\n const data = await api('/api/v6/providers');\n if (!data) return;\n _providers = data.providers ?? [];\n\n // Discovered agents section\n document.getElementById('discovered-agents').innerHTML = data.discovered.length\n ? data.discovered.map(d => `<div class=\"row\"><span class=\"k\"><span class=\"pill purple\">${d.agent}</span> \u2192 <span class=\"pill cyan\">${d.provider}</span> <span class=\"tag-code\">${d.model || '(none)'}</span></span><span class=\"v mono\" style=\"color:var(--text-3)\" title=\"${d.sourcePath}\">${d.apiKey ? '\uD83D\uDD11 API key available' : 'no API key found'}</span></div>`).join('')\n : '<div class=\"empty\">No agent configs found yet. Install Claude Code / Cursor / Cline / Codex / Aider / Kilo / Roo / OpenCode, or add a custom path below.</div>';\n\n // Provider cards\n document.getElementById('providers-grid').innerHTML = _providers.map(p => {\n const okClass = p.apiKey ? 'good' : p.id === 'ollama' ? '' : 'warn';\n return `<div class=\"card\" style=\"padding:14px\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:8px\"><div style=\"font-weight:700;font-size:13px\">${p.displayName}</div><span class=\"pill ${p.enabled ? 'green' : ''}\">${p.enabled ? 'enabled' : 'off'}</span></div>\n <div class=\"field\" style=\"margin-bottom:6px\"><label>API Key ${p.source === 'agent-discovered' ? '(from ' + (p.discoveredFrom || 'agent') + ')' : p.source === 'env' ? '(env)' : ''}</label><input type=\"password\" id=\"pkey-${p.id}\" placeholder=\"${p.apiKey ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : 'sk-...'}\" value=\"${p.apiKey || ''}\"/></div>\n <div class=\"field\" style=\"margin-bottom:6px\"><label>Default Model</label><input id=\"pmodel-${p.id}\" value=\"${p.defaultModel || ''}\"/></div>\n <div class=\"flex gap\">\n <button class=\"btn sm primary\" onclick=\"saveProvider('${p.id}')\">Save</button>\n <button class=\"btn sm\" onclick=\"testProvider('${p.id}')\">Test</button>\n <button class=\"btn sm\" onclick=\"fetchProviderModels('${p.id}')\">Fetch Models</button>\n </div>\n <div class=\"mono mt\" id=\"pmodels-${p.id}\" style=\"max-height:120px;overflow-y:auto;font-size:10.5px;color:var(--text-3)\"></div>\n </div>`;\n }).join('');\n\n // Intelligence config\n const intel = await api('/api/v6/intelligence');\n if (intel) {\n const provSel = document.getElementById('intel-provider');\n provSel.innerHTML = _providers.map(p => `<option value=\"${p.id}\" ${intel.leadProvider === p.id ? 'selected' : ''}>${p.displayName}</option>`).join('');\n // Populate model field\n const modelInput = document.getElementById('intel-model');\n modelInput.innerHTML = `<option value=\"${intel.leadModel}\">${intel.leadModel}</option>`;\n document.getElementById('intel-agent-proxy').checked = intel.useAgentToolModels;\n document.getElementById('intel-local-first').checked = intel.preferLocalFirst;\n }\n\n // Custom agents\n const ca = await api('/api/v6/custom-agents');\n const agents = ca?.agents ?? [];\n document.getElementById('custom-agents-list').innerHTML = agents.length\n ? agents.map((a, i) => `<div class=\"row\"><span class=\"k\"><span class=\"pill purple\">${a.name}</span> <span class=\"tag-code\">${a.path}</span></span><span class=\"v\"><button class=\"btn sm danger\" onclick=\"removeCustomAgent(${i})\">Remove</button></span></div>`).join('')\n : '<div class=\"empty\">No custom agent paths yet</div>';\n}\nasync function saveProvider(id) {\n const apiKey = document.getElementById('pkey-' + id).value;\n const defaultModel = document.getElementById('pmodel-' + id).value;\n const r = await api('/api/v6/provider-save', { provider: id, apiKey: apiKey || undefined, defaultModel, enabled: true });\n if (r) { toast('Saved', id); refreshModels(); }\n}\nasync function testProvider(id) {\n const r = await api('/api/v6/provider-test', { provider: id });\n toast(r?.ok ? 'Connected' : 'Failed', r?.ok ? (r.modelCount + ' models available') : (r?.error || 'Unknown error'), r?.ok ? 'success' : 'error');\n}\nasync function fetchProviderModels(id) {\n const models = await api('/api/v6/provider-models', { provider: id });\n if (!models) return;\n _allModels[id] = models;\n document.getElementById('pmodels-' + id).innerHTML = models.length\n ? models.slice(0, 50).map(m => `<div style=\"padding:3px 0\"><span class=\"tag-code\">${m.id}</span>${m.tags.length ? ' <span style=\"color:var(--text-dim)\">' + m.tags.join(' \u00B7 ') + '</span>' : ''}</div>`).join('')\n : '<div>No models (check API key + network)</div>';\n toast('Fetched', models.length + ' models from ' + id);\n}\nasync function saveIntelligence() {\n const provId = document.getElementById('intel-provider').value;\n const model = document.getElementById('intel-model').value;\n const useAgent = document.getElementById('intel-agent-proxy').checked;\n const local = document.getElementById('intel-local-first').checked;\n const r = await api('/api/v6/intelligence', {\n leadProvider: provId, leadModel: model,\n useAgentToolModels: useAgent, preferLocalFirst: local,\n });\n document.getElementById('intel-result').textContent = r ? '\u2713 Intelligence config saved \u00B7 lead: ' + provId + ' / ' + model : '\u2717 Save failed';\n if (r) toast('Intelligence updated');\n}\nasync function addCustomAgent() {\n const name = document.getElementById('ca-name').value.trim();\n const p = document.getElementById('ca-path').value.trim();\n if (!name || !p) return toast('Missing', 'Name + path required', 'warn');\n const cur = await api('/api/v6/custom-agents');\n const agents = [...(cur?.agents || []), { name, path: p }];\n await api('/api/v6/custom-agents', { agents });\n document.getElementById('ca-name').value = ''; document.getElementById('ca-path').value = '';\n toast('Added', name); refreshModels();\n}\nasync function removeCustomAgent(i) {\n const cur = await api('/api/v6/custom-agents');\n const agents = (cur?.agents || []).filter((_, idx) => idx !== i);\n await api('/api/v6/custom-agents', { agents });\n toast('Removed', '', 'warn'); refreshModels();\n}\n\n// \u2500\u2500\u2500\u2500 Chat with Brain \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet currentConversationId = null;\nasync function refreshChat() {\n const list = await api('/api/v6/chat-conversations');\n const el = document.getElementById('chat-conversations');\n el.innerHTML = (list || []).map(c => `<div class=\"row\" onclick=\"loadConv('${c.id}')\" style=\"cursor:pointer\"><span class=\"k\" style=\"min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">${c.preview || '(new)'}</span><span class=\"v mono\" style=\"font-size:10px\">${c.turnCount}</span></div>`).join('') || '<div class=\"empty\">No chats yet</div>';\n if (!currentConversationId) renderChatMessages([]);\n}\nasync function loadConv(id) { currentConversationId = id; const turns = await api('/api/v6/chat-conversation', { conversationId: id }) ?? []; renderChatMessages(turns); }\nfunction newChat() { currentConversationId = null; renderChatMessages([]); document.getElementById('chat-input').focus(); }\nasync function clearAllChats() { if (!confirm('Clear ALL chat history?')) return; await api('/api/v6/chat-clear-all', {}); currentConversationId = null; refreshChat(); renderChatMessages([]); toast('Chat history cleared','','warn'); }\nfunction renderChatMessages(turns) {\n const el = document.getElementById('chat-messages');\n if (!turns.length) { el.innerHTML = '<div class=\"empty\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>Ask your brain anything.<br/>It will search memories + cite them.</div>'; return; }\n el.innerHTML = turns.map(t => {\n const isUser = t.role === 'user';\n const cites = (t.citations || []).length ? `<div style=\"margin-top:8px;padding:8px;background:var(--bg-elev-1);border-radius:6px\"><div style=\"font-size:10px;color:var(--text-3);margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em\">Citations (${t.citations.length})</div>${t.citations.map((c, i) => `<div style=\"padding:4px 0;border-bottom:1px solid var(--border);font-size:11px\"><span class=\"pill cyan\">[${i+1}]</span> <span class=\"pill\">${c.agent}/${c.category}</span> ${c.content.slice(0, 180)} <span style=\"color:var(--text-dim)\">(${(c.score*100).toFixed(0)}%)</span></div>`).join('')}</div>` : '';\n const meta = !isUser && t.provider ? `<div class=\"mono\" style=\"font-size:10px;color:var(--text-3);margin-top:6px\">${t.provider}/${t.model || ''} \u00B7 ${t.tokensUsed || 0} tok${t.cached ? ' \u00B7 cached' : ''}</div>` : '';\n return `<div style=\"display:flex;gap:10px;margin-bottom:14px;${isUser ? '' : 'background:var(--bg-elev-1);padding:12px;border-radius:10px'}\"><div style=\"width:28px;height:28px;border-radius:50%;background:${isUser ? 'var(--brand-gradient)' : 'var(--bg-elev-3)'};display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:700\">${isUser ? 'You' : '\uD83E\uDDE0'}</div><div style=\"flex:1;min-width:0\"><div style=\"white-space:pre-wrap;font-size:13px;line-height:1.5\">${(t.content || '').replace(/\\</g, '<')}</div>${cites}${meta}</div></div>`;\n }).join('');\n el.scrollTop = el.scrollHeight;\n}\nasync function sendChat() {\n const input = document.getElementById('chat-input');\n const question = input.value.trim();\n if (!question) return;\n input.value = '';\n const btn = document.getElementById('chat-send-btn');\n btn.disabled = true;\n document.getElementById('chat-status').textContent = 'Searching memories + calling LLM...';\n const turns = currentConversationId ? (await api('/api/v6/chat-conversation', { conversationId: currentConversationId }) ?? []) : [];\n turns.push({ role: 'user', content: question, createdAt: new Date() });\n renderChatMessages(turns);\n try {\n const res = await api('/api/v6/chat', { question, conversationId: currentConversationId, maxCitations: 6 });\n if (res) {\n currentConversationId = res.conversationId;\n const updated = await api('/api/v6/chat-conversation', { conversationId: currentConversationId }) ?? [];\n renderChatMessages(updated);\n refreshChat();\n document.getElementById('chat-status').textContent = 'Answered in ' + (res.ms || '?') + 'ms \u00B7 ' + (res.citations?.length || 0) + ' citations';\n }\n } finally {\n btn.disabled = false;\n }\n}\n\n// \u2500\u2500\u2500\u2500 Stop button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function stopBrain() {\n if (!confirm('Stop Shadow Brain?\\n\\nThe npm process will exit. You will need to restart it from your terminal.')) return;\n try {\n await fetch('/api/v6/shutdown', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });\n } catch { /* server is shutting down */ }\n toast('Shutting down', 'Shadow Brain is stopping...', 'warn');\n setTimeout(() => { document.body.style.filter = 'grayscale(1) brightness(0.4)'; document.body.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100vh;color:#888;font-family:ui-monospace,monospace;font-size:16px;flex-direction:column;gap:16px\"><div>\u23FB Shadow Brain stopped</div><div style=\"font-size:12px\">Restart with: <code style=\"background:#222;padding:4px 8px;border-radius:4px\">npm run dev</code> or <code style=\"background:#222;padding:4px 8px;border-radius:4px\">shadow-brain dash .</code></div></div>'; }, 1500);\n}\n\n// When the intel provider changes, re-fetch its models to populate the model dropdown\ndocument.addEventListener('change', async e => {\n if (e.target && e.target.id === 'intel-provider') {\n const id = e.target.value;\n const models = _allModels[id] || await api('/api/v6/provider-models', { provider: id });\n _allModels[id] = models || [];\n const sel = document.getElementById('intel-model');\n if (sel) sel.innerHTML = (models || []).map(m => `<option value=\"${m.id}\">${m.displayName}</option>`).join('') || '<option>no models available</option>';\n }\n});\n\n// \u2500\u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshConfig() {\n const hive = state.hive || await api('/api/v6/hive-status');\n if (!hive) return;\n document.getElementById('config-state').innerHTML = `\n <div class=\"row\"><span class=\"k\">Version</span><span class=\"v\">${hive.version}</span></div>\n <div class=\"row\"><span class=\"k\">Local-first</span><span class=\"v ${hive.localFirst ? 'good' : 'warn'}\">${hive.localFirst ? 'YES' : 'no'}</span></div>\n <div class=\"row\"><span class=\"k\">Agents connected</span><span class=\"v\">${hive.totalAgentsConnected}</span></div>\n <div class=\"row\"><span class=\"k\">Memories stored</span><span class=\"v\">${hive.totalMemoriesStored}</span></div>\n <div class=\"row\"><span class=\"k\">Modules active</span><span class=\"v good\">${Object.keys(hive.modules || {}).length}</span></div>`;\n}\nasync function saveConfig() { const r = await api('/api/v6/config-save', { provider: document.getElementById('cfg-provider').value, apiKey: document.getElementById('cfg-apikey').value || undefined, model: document.getElementById('cfg-model').value || undefined }); document.getElementById('cfg-result').textContent = r?.ok ? '\u2713 Saved' : '\u2717 Failed'; if (r?.ok) toast('Config saved'); }\nasync function testConnection() { const r = await api('/api/v6/config-test', { provider: document.getElementById('cfg-provider').value }); document.getElementById('cfg-result').textContent = r?.ok ? '\u2713 Connected' : '\u2717 ' + (r?.error || 'Failed'); toast(r?.ok ? 'Connection OK' : 'Connection failed', r?.error || '', r?.ok ? 'success' : 'error'); }\nasync function startMCP() { const r = await api('/api/v6/mcp-start', { port: parseInt(document.getElementById('mcp-port').value || '7342'), authToken: document.getElementById('mcp-token').value || undefined }); document.getElementById('mcp-result').textContent = r?.ok ? '\u2713 Started at :' + (r.port ?? 7342) : '\u2717 ' + (r?.error || 'Failed'); toast(r?.ok ? 'MCP started' : 'MCP failed', r?.error, r?.ok ? 'success' : 'error'); }\nasync function stopMCP() { const r = await api('/api/v6/mcp-stop', {}); document.getElementById('mcp-result').textContent = r?.ok ? '\u2713 Stopped' : '\u2717 ' + (r?.error || 'Failed'); toast('MCP stopped', '', 'warn'); }\n\n// \u2500\u2500\u2500\u2500 Agents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshAgents() {\n const list = await api('/api/v6/agents-list', {}) ?? [];\n document.getElementById('badge-agents').textContent = list.filter(a => a.hookInstalled).length;\n document.getElementById('agents-table').innerHTML = list.map(a => `\n <tr>\n <td><strong>${a.displayName}</strong><br/><code style=\"color:var(--text-3);font-size:10px\">${a.name}</code></td>\n <td>${a.detected ? '<span class=\"pill green\">detected</span>' : '<span class=\"pill\">not found</span>'}</td>\n <td>${a.hookInstalled ? '<span class=\"pill cyan\">active</span>' : '<span class=\"pill\">not wired</span>'}</td>\n <td class=\"mono\">${a.hookPath ? a.hookPath.slice(0, 70) : '\u2014'}</td>\n <td style=\"text-align:right\">${a.hookInstalled ? `<button class=\"btn sm danger\" onclick=\"detachOne('${a.name}')\">Disconnect</button>` : `<button class=\"btn sm success\" onclick=\"attachOne('${a.name}')\">Connect</button>`}</td>\n </tr>`).join('') || '<tr><td colspan=\"5\"><div class=\"empty\">No agents detected</div></td></tr>';\n}\nasync function attachOne(agent) { await api('/api/v6/agents-attach', { agent }); toast('Attached', agent); refreshAgents(); }\nasync function detachOne(agent) { await api('/api/v6/agents-detach', { agent }); toast('Detached', agent, 'warn'); refreshAgents(); }\nasync function attachAll() { const r = await api('/api/v6/agents-attach-all', {}); if (r) { toast('Attach All', (r.attached?.length ?? 0) + ' agents wired'); refreshAgents(); } }\nasync function detachAll() { if (!confirm('Detach Shadow Brain hooks from ALL agents?')) return; const r = await api('/api/v6/agents-detach-all', {}); if (r) { toast('Detach All', (r.attached?.length ?? 0) + ' agents', 'warn'); refreshAgents(); } }\n\n// \u2500\u2500\u2500\u2500 Memory browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction renderCountRows(rows, emptyMessage) {\n if (!rows || !rows.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return rows.slice(0, 8).map(row =>\n '<div class=\"row\"><span class=\"k\">' + htmlEscape(row.name) + '</span><span class=\"v\">' + htmlEscape(row.count) + '</span></div>'\n ).join('');\n}\nasync function refreshMemoryIntelligence() {\n const data = await api('/api/v6/memory-intelligence?limit=500');\n if (!data) return;\n document.getElementById('mem-total').textContent = data.totalEntries ?? 0;\n document.getElementById('mem-sampled').textContent = (data.sampledEntries ?? 0) + ' shown in intelligence sample';\n document.getElementById('mem-source').textContent = Math.round((data.sourceCoverage || 0) * 100) + '%';\n document.getElementById('mem-fresh').textContent = data.recent7d ?? 0;\n document.getElementById('mem-seed-open').textContent = data.openSeedDrafts ?? 0;\n document.getElementById('mem-seed-sub').textContent = (data.pendingApprovals ?? 0) + ' need approval';\n document.getElementById('mem-categories').innerHTML = renderCountRows(data.byCategory, 'No categories yet.');\n document.getElementById('mem-agents').innerHTML = renderCountRows(data.byAgent, 'No agent memory writes yet.');\n document.getElementById('mem-tags').innerHTML = renderCountRows(data.topTags, 'No tags yet.');\n}\nasync function searchMemory() {\n const r = await api('/api/v6/memory-browser', {\n query: document.getElementById('mem-query').value,\n agent: document.getElementById('mem-agent').value,\n category: document.getElementById('mem-category').value,\n minImportance: parseFloat(document.getElementById('mem-importance').value || '0'),\n });\n if (!r) return;\n document.getElementById('mem-results').innerHTML = r.length ? r.map(m =>\n `<tr><td><code>${m.id.slice(0, 10)}</code></td><td><span class=\"pill cyan\">${m.agentTool}</span></td><td>${m.category}</td><td>${(m.content || '').slice(0, 140)}</td><td>${(m.importance || 0).toFixed(2)}</td><td class=\"mono\">${new Date(m.createdAt).toLocaleDateString()}</td></tr>`\n ).join('') : '<tr><td colspan=\"6\"><div class=\"empty\">No memories match your filters</div></td></tr>';\n}\n\n// \u2500\u2500\u2500\u2500 Activity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Live Brain\n// Memory Seeder\nfunction seedProjectPath() {\n return document.getElementById('seed-project-path')?.value?.trim()\n || document.getElementById('live-project-path')?.value?.trim()\n || document.getElementById('ww2-project-path')?.value?.trim()\n || '';\n}\nfunction copyLivePathToSeeder() {\n const value = document.getElementById('live-project-path')?.value?.trim()\n || document.getElementById('ww2-project-path')?.value?.trim()\n || '';\n if (value) document.getElementById('seed-project-path').value = value;\n}\nfunction openSeederForLiveBrain() {\n const projectDir = liveProjectPath() || document.getElementById('ww2-project-path')?.value?.trim() || '';\n switchTab('seeder', 'Memory Seeder');\n if (projectDir) document.getElementById('seed-project-path').value = projectDir;\n const topic = document.getElementById('seed-topic');\n const objective = document.getElementById('seed-objective');\n if (topic && !topic.value) topic.value = 'Source-backed live brain improvement';\n if (objective && !objective.value) objective.value = 'Capture verified project knowledge that future agents should recall before complex work.';\n refreshSeedDrafts();\n}\nfunction seedDraftStatusClass(status) {\n if (status === 'seeded') return 'green';\n if (status === 'approved') return 'cyan';\n if (status === 'rejected') return 'red';\n return 'yellow';\n}\nfunction renderSeedStatus(drafts) {\n const counts = { draft: 0, approved: 0, seeded: 0, rejected: 0 };\n for (const d of drafts || []) counts[d.status] = (counts[d.status] || 0) + 1;\n return [\n '<div class=\"row\"><span class=\"k\">Open drafts</span><span class=\"v warn\">' + counts.draft + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Approved</span><span class=\"v good\">' + counts.approved + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Seeded</span><span class=\"v good\">' + counts.seeded + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Rejected</span><span class=\"v bad\">' + counts.rejected + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Current project</span><span class=\"v mono\">' + htmlEscape(seedProjectPath() || 'dashboard default') + '</span></div>',\n ].join('');\n}\nfunction seedJsArg(value) {\n return JSON.stringify(String(value || ''));\n}\nfunction renderSeedDrafts(drafts) {\n if (!drafts || !drafts.length) return '<div class=\"empty-tight\">No seed drafts yet.</div>';\n return drafts.map(d => {\n const approvedBy = new Set((d.approvals || []).map(a => a.by));\n const required = d.requiredApprovals || [];\n const pending = required.filter(a => !approvedBy.has(a));\n const agentApprover = d.approverAgent && d.approverAgent !== 'user' ? d.approverAgent : d.researchAgent;\n const warnings = (d.warnings || []).length\n ? '<div class=\"warning-stack\" style=\"margin-top:8px\">' + d.warnings.map(w => '<div class=\"warning-banner\"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'\n : '';\n const sources = (d.sources || []).slice(0, 5).map(s => '<code>' + htmlEscape(s) + '</code>').join(' ');\n let actions = '';\n if (d.status === 'draft') {\n actions += '<button class=\"btn sm success\" onclick=\"approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg('user') + ')\">User Approve</button> ';\n actions += '<button class=\"btn sm\" onclick=\"approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg(agentApprover) + ')\">Agent Approve</button> ';\n actions += '<button class=\"btn sm danger\" onclick=\"rejectSeedDraft(' + seedJsArg(d.id) + ')\">Reject</button>';\n }\n if (d.status === 'approved') {\n actions += '<button class=\"btn sm primary\" onclick=\"commitSeedDraft(' + seedJsArg(d.id) + ')\">Seed to Brain</button> ';\n actions += '<button class=\"btn sm danger\" onclick=\"rejectSeedDraft(' + seedJsArg(d.id) + ')\">Reject</button>';\n }\n if (d.status === 'seeded') {\n actions += '<span class=\"pill green\">memory ' + htmlEscape((d.seededMemoryId || '').slice(0, 10)) + '</span> ';\n actions += '<button class=\"btn sm\" onclick=\"handoffToAgent(' + seedJsArg(d.researchAgent) + ')\" title=\"Generate a briefing to hand this knowledge back to an agent\">\u21A9 Hand to Agent</button>';\n }\n return '<div class=\"memory-item\">' +\n '<div class=\"meta\">' +\n '<span class=\"pill ' + seedDraftStatusClass(d.status) + '\">' + htmlEscape(d.status) + '</span>' +\n '<span class=\"pill purple\">' + htmlEscape(d.researchAgent) + '</span>' +\n '<span class=\"pill cyan\">' + htmlEscape(d.approvalMode) + '</span>' +\n '<span>' + htmlEscape(new Date(d.updatedAt || d.createdAt).toLocaleString()) + '</span>' +\n '</div>' +\n '<div style=\"display:flex;gap:12px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap\">' +\n '<div style=\"min-width:260px;flex:1\">' +\n '<div style=\"font-weight:700;margin-bottom:4px\">' + htmlEscape(d.topic) + '</div>' +\n '<div class=\"content\">' + htmlEscape(d.objective || '').slice(0, 260) + '</div>' +\n '<div class=\"mono\" style=\"font-size:11px;color:var(--text-3);margin-top:6px\">pending: ' + htmlEscape(pending.join(', ') || 'none') + ' | category: ' + htmlEscape(d.category) + '</div>' +\n '<div style=\"font-size:11px;color:var(--text-3);margin-top:6px\">' + (sources || 'No sources') + '</div>' +\n '</div>' +\n '<div style=\"display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end\">' + actions + '</div>' +\n '</div>' +\n warnings +\n '<pre class=\"mono\" style=\"margin-top:10px;max-height:180px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px;white-space:pre-wrap\">' + htmlEscape(d.content || '') + '</pre>' +\n '</div>';\n }).join('');\n}\nasync function refreshSeedDrafts() {\n const input = document.getElementById('seed-project-path');\n if (input && !input.value) input.value = seedProjectPath();\n const drafts = await api('/api/v6/seed-drafts?limit=50') ?? [];\n const open = drafts.filter(d => d.status === 'draft' || d.status === 'approved').length;\n const badge = document.getElementById('badge-seeder');\n if (badge) badge.textContent = open;\n const status = document.getElementById('seed-status');\n if (status) status.innerHTML = renderSeedStatus(drafts);\n const list = document.getElementById('seed-drafts');\n if (list) list.innerHTML = renderSeedDrafts(drafts);\n}\nasync function createSeedDraft() {\n const topic = document.getElementById('seed-topic').value.trim();\n if (!topic) { toast('Topic required', 'Add a seed topic before creating a draft.', 'warn'); return; }\n const draft = await api('/api/v6/seed-draft', {\n projectDir: seedProjectPath(),\n topic,\n objective: document.getElementById('seed-objective').value,\n researchAgent: document.getElementById('seed-research-agent').value,\n approverAgent: document.getElementById('seed-approver-agent').value,\n approvalMode: document.getElementById('seed-approval-mode').value,\n category: document.getElementById('seed-category').value,\n tags: document.getElementById('seed-tags').value,\n importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),\n sources: document.getElementById('seed-sources').value,\n content: document.getElementById('seed-content').value,\n });\n if (draft) {\n toast('Seed draft created', draft.status);\n refreshSeedDrafts();\n }\n}\n// One-click path: create \u2192 approve \u2192 save to brain. The \"single seed\".\nasync function quickSeed() {\n const topic = document.getElementById('seed-topic').value.trim();\n if (!topic) { toast('Topic required', 'Add a topic before quick-saving.', 'warn'); return; }\n const draft = await api('/api/v6/seed-draft', {\n projectDir: seedProjectPath(),\n topic,\n objective: document.getElementById('seed-objective').value,\n researchAgent: document.getElementById('seed-research-agent').value,\n approverAgent: 'user',\n approvalMode: 'user',\n category: document.getElementById('seed-category').value,\n tags: document.getElementById('seed-tags').value,\n importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),\n sources: document.getElementById('seed-sources').value,\n content: document.getElementById('seed-content').value,\n });\n if (!draft || !draft.id) return;\n const approved = await api('/api/v6/seed-approve', { id: draft.id, approvedBy: 'user' });\n if (!approved) { toast('Saved as draft', 'Approve it in the queue to finish.', 'warn'); refreshSeedDrafts(); return; }\n const committed = await api('/api/v6/seed-commit', { id: draft.id, seededBy: 'user' });\n if (committed) {\n toast('Saved to brain \u26A1', 'Knowledge is now in memory.', 'success');\n refreshSeedDrafts();\n try { refreshLiveBrain(); } catch (e) {}\n } else {\n toast('Approved', 'Couldn\\'t finalize \u2014 finish from the queue.', 'warn');\n refreshSeedDrafts();\n }\n}\nasync function approveSeedDraft(id, approvedBy) {\n const draft = await api('/api/v6/seed-approve', { id, approvedBy });\n if (draft) {\n toast('Approval recorded', draft.status);\n refreshSeedDrafts();\n }\n}\nasync function rejectSeedDraft(id) {\n const reason = prompt('Reject reason', 'Not verified enough');\n if (reason === null) return;\n const draft = await api('/api/v6/seed-reject', { id, reason, rejectedBy: 'user' });\n if (draft) {\n toast('Draft rejected', draft.topic, 'warn');\n refreshSeedDrafts();\n }\n}\nasync function commitSeedDraft(id) {\n const draft = await api('/api/v6/seed-commit', { id, seededBy: 'user' });\n if (draft) {\n toast('Seeded to brain', (draft.seededMemoryId || '').slice(0, 10));\n refreshSeedDrafts();\n refreshLiveBrain();\n }\n}\n// Two-way: hand the brain's knowledge back to an agent as a ready briefing.\nasync function handoffToAgent(toAgent) {\n const md = await api('/api/v6/agent-handoff', { projectDir: seedProjectPath(), fromAgent: 'user', toAgent: toAgent || 'claude-code', format: 'markdown' });\n if (!md) return;\n try {\n await navigator.clipboard.writeText(typeof md === 'string' ? md : JSON.stringify(md, null, 2));\n toast('Handoff copied', 'Briefing for ' + (toAgent || 'agent') + ' is on your clipboard \u2014 paste it into the agent.', 'success');\n } catch (e) {\n toast('Handoff ready', 'Copy it from the Activity log if clipboard is blocked.', 'warn');\n }\n}\n\nfunction liveProjectPath() {\n return document.getElementById('live-project-path')?.value?.trim() || '';\n}\nfunction liveActionPill(priority) {\n if (priority === 'critical' || priority === 'high') return 'red';\n if (priority === 'medium') return 'yellow';\n return 'cyan';\n}\nasync function refreshLiveBrain() {\n const projectDir = liveProjectPath();\n const queries = document.getElementById('live-queries')?.value?.trim() || '';\n const url = '/api/v6/live-brain?projectDir=' + encodeURIComponent(projectDir) + (queries ? '&queries=' + encodeURIComponent(queries) : '');\n const data = await api(url);\n if (!data) return;\n\n const quality = data.memoryQuality || {};\n document.getElementById('live-memories').textContent = quality.totalEntries ?? 0;\n document.getElementById('live-quality').textContent = (quality.avgScore ?? 0) + '/100';\n document.getElementById('live-sources').textContent = Math.round((quality.sourceCoverage ?? 0) * 100) + '%';\n document.getElementById('live-recall').textContent = data.recallEval?.avgHitCount ?? 0;\n document.getElementById('live-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';\n\n document.getElementById('live-actions').innerHTML = (data.learningActions || []).length\n ? data.learningActions.map(a => `<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill ${liveActionPill(a.priority)}\">${a.priority}</span><span>${a.title}</span></div><div class=\"content\">${a.reason}</div>${a.command ? '<code>' + a.command + '</code>' : ''}</div>`).join('')\n : '<div class=\"empty\">No immediate hygiene actions.</div>';\n\n document.getElementById('live-eval').innerHTML = (data.recallEval?.cases || []).map(c =>\n `<div class=\"row\"><span class=\"k\">${c.query}</span><span class=\"v ${c.hitCount ? 'good' : 'bad'}\">${c.hitCount} hits - ${c.sourceBackedHits} sourced</span></div>`\n ).join('') || '<div class=\"empty\">No eval cases.</div>';\n\n document.getElementById('live-top').innerHTML = (data.topMemories || []).slice(0, 8).map(m =>\n `<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill purple\">${m.agentTool}</span><span>${m.category}</span><span>score ${m.qualityScore}/100</span></div><div class=\"content\">${(m.content || '').slice(0, 260)}</div></div>`\n ).join('') || '<div class=\"empty\">No memories.</div>';\n\n document.getElementById('live-brief').textContent = data.agentBriefing || '';\n}\n\n// \u2500\u2500\u2500\u2500 Realtime Activity Stream (v6.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet activityBuffer = [];\nconst activitySeen = new Set();\nconst activitySeenOrder = [];\nlet activityFilter = '';\nlet activityPaused = false;\nlet activityRate = [];\nlet lastIngestFresh = false;\n\nfunction activityKey(e) { return (e.type || '') + '|' + (e.source || '') + '|' + String(e.detail || '').slice(0, 60); }\nfunction actEpoch(e) {\n if (typeof e.ts === 'number') return e.ts;\n if (e.timestamp) { const t = Date.parse(e.timestamp); if (!isNaN(t)) return t; }\n return Date.now();\n}\nfunction ingestActivity(raw, fresh) {\n const e = { ts: actEpoch(raw), type: raw.type || 'event', source: raw.source || 'brain', detail: String(raw.detail || '') };\n const key = activityKey(e);\n if (activitySeen.has(key)) return false;\n activitySeen.add(key); activitySeenOrder.push(key);\n if (activitySeenOrder.length > 1200) { const drop = activitySeenOrder.splice(0, activitySeenOrder.length - 1200); for (const k of drop) activitySeen.delete(k); }\n activityBuffer.unshift(e);\n if (activityBuffer.length > 500) activityBuffer.length = 500;\n activityRate.push(e.ts);\n const cutoff = Date.now() - 30 * 60 * 1000;\n while (activityRate.length && activityRate[0] < cutoff) activityRate.shift();\n if (!activityPaused) {\n appendLog(e.source, e.type, e.detail);\n if (fresh) {\n try {\n if (typeof graphTimer !== 'undefined' && graphTimer) {\n const col = signalColors[e.type] || '#18ffff';\n const srcExists = graphNodes.find(n => n.id === e.source);\n graphSignals.push({ fromId: srcExists ? e.source : 'brain', toId: 'brain', type: e.type, color: col, progress: 0, spawnedAt: Date.now() });\n if (graphSignals.length > 100) graphSignals = graphSignals.slice(-100);\n }\n } catch (err) {}\n lastIngestFresh = true;\n }\n }\n return true;\n}\nfunction typeMatchesFilter(type, filter) {\n if (!filter) return true;\n if (filter === 'seed-draft') return type === 'seed-draft' || type === 'seed-commit';\n return type === filter;\n}\nfunction renderActivityTable() {\n const tbody = document.getElementById('activity-table');\n if (!tbody) return;\n const searchEl = document.getElementById('act-search');\n const q = (searchEl ? searchEl.value : '').toLowerCase().trim();\n let rows = activityBuffer.filter(e => typeMatchesFilter(e.type, activityFilter));\n if (q) rows = rows.filter(e => (e.source + ' ' + e.type + ' ' + e.detail).toLowerCase().includes(q));\n const fresh = lastIngestFresh; lastIngestFresh = false;\n rows = rows.slice(0, 200);\n tbody.innerHTML = rows.length\n ? rows.map((e, i) => '<tr' + (i === 0 && fresh ? ' class=\"act-fresh\"' : '') + '><td class=\"mono\">' + new Date(e.ts).toLocaleTimeString() + '</td><td><span class=\"pill purple\">' + htmlEscape(e.source) + '</span></td><td><span class=\"pill ' + typeToPill(e.type) + '\">' + htmlEscape(e.type) + '</span></td><td class=\"mono\">' + htmlEscape(e.detail.slice(0, 200)) + '</td></tr>').join('')\n : '<tr><td colspan=\"4\"><div class=\"empty\">No activity</div></td></tr>';\n const total = document.getElementById('act-stat-total'); if (total) total.textContent = activityBuffer.length;\n const last = document.getElementById('act-stat-last'); if (last) last.textContent = activityBuffer[0] ? new Date(activityBuffer[0].ts).toLocaleTimeString() : '\u2014';\n}\nfunction eventsPerMin() { const c = Date.now() - 60000; return activityRate.filter(t => t >= c).length; }\nfunction renderSparkline() {\n const rate = eventsPerMin();\n const ratePill = document.getElementById('act-rate-pill'); if (ratePill) ratePill.textContent = rate + ' / min';\n const ovRate = document.getElementById('ov-pulse-rate'); if (ovRate) ovRate.textContent = rate + ' / min';\n const cv = document.getElementById('act-spark');\n if (!cv || !cv.getContext) return;\n const now = Date.now();\n const buckets = new Array(10).fill(0);\n for (const t of activityRate) { const age = now - t; if (age < 0 || age > 10 * 60000) continue; const idx = 9 - Math.floor(age / 60000); if (idx >= 0 && idx < 10) buckets[idx]++; }\n const pw = (cv.parentElement ? cv.parentElement.clientWidth : 0) || 600;\n cv.width = pw; cv.height = 120;\n const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; ctx.clearRect(0, 0, W, H);\n let max = 1; for (const b of buckets) if (b > max) max = b;\n const bw = W / 10;\n for (let i = 0; i < 10; i++) {\n const h = (buckets[i] / max) * (H - 12);\n const x = i * bw + 3, y = H - h;\n ctx.fillStyle = i === 9 ? '#18ffff' : 'rgba(168,85,247,0.55)';\n ctx.fillRect(x, y, bw - 6, Math.max(0, h));\n }\n}\nfunction renderLivePulse() {\n const el = document.getElementById('ov-pulse');\n if (!el) return;\n const items = activityBuffer.slice(0, 8);\n el.innerHTML = items.length\n ? items.map(e => '<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill ' + typeToPill(e.type) + '\">' + htmlEscape(e.type) + '</span><span class=\"pill purple\">' + htmlEscape(e.source) + '</span><span>' + new Date(e.ts).toLocaleTimeString() + '</span></div><div class=\"content\">' + htmlEscape(e.detail.slice(0, 200)) + '</div></div>').join('')\n : '<div class=\"empty-tight\">Awaiting live activity\u2026</div>';\n}\nfunction setActivityFilter(btn, type) {\n activityFilter = type;\n const chips = document.querySelectorAll('#act-chips .chip');\n chips.forEach(c => c.classList.toggle('active', c === btn));\n renderActivityTable();\n}\nfunction toggleActivityPause() {\n activityPaused = !activityPaused;\n const btn = document.getElementById('act-pause-btn'); if (btn) btn.textContent = activityPaused ? 'Resume' : 'Pause';\n const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', activityPaused); badge.textContent = activityPaused ? 'PAUSED' : 'LIVE'; }\n const live = document.getElementById('act-stat-live'); if (live) { live.textContent = activityPaused ? 'paused' : 'streaming'; live.className = 'v ' + (activityPaused ? 'warn' : 'good'); }\n if (!activityPaused) renderActivityTable();\n}\nfunction clearActivity() {\n activityBuffer = []; activitySeen.clear(); activitySeenOrder.length = 0; activityRate = [];\n renderActivityTable(); renderSparkline(); renderLivePulse();\n}\nfunction setConn(online) {\n const d = document.getElementById('conn-dot'); if (d) { d.classList.toggle('offline', !online); d.title = 'Realtime connection: ' + (online ? 'live' : 'reconnecting'); }\n if (activityPaused) return;\n const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', !online); badge.textContent = online ? 'LIVE' : 'OFFLINE'; }\n const live = document.getElementById('act-stat-live'); if (live) { live.textContent = online ? 'streaming' : 'reconnecting'; live.className = 'v ' + (online ? 'good' : 'bad'); }\n}\nasync function refreshActivity() {\n const log = await api('/api/v6/activity-log?limit=200');\n if (Array.isArray(log)) for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);\n renderActivityTable(); renderSparkline();\n}\n\n// \u2500\u2500\u2500\u2500 Live Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet graphFrame = 0, graphNodes = [], graphEdges = [], graphSignals = [], graphTimer = null, graphPullTimer = null;\nconst signalColors = { 'memory-write':'#10e68c','subagent-spawn':'#18ffff','dream':'#a855f7','revisit':'#a855f7','counterfactual':'#a855f7','consolidation':'#a855f7','contradiction':'#ff5864','pattern-discovery':'#10e68c','collision':'#ff5864','quarantine':'#ec4899','firewall-block':'#ffb53f' };\nasync function startGraph() { await pullTopology(); if (graphTimer) cancelAnimationFrame(graphTimer); graphLoop(); if (graphPullTimer) clearInterval(graphPullTimer); graphPullTimer = setInterval(pullTopology, 3000); }\nasync function pullTopology() {\n const topo = await api('/api/v6/topology', {});\n if (!topo) return;\n const canvas = document.getElementById('graph-canvas');\n if (!canvas) return;\n const W = canvas.width, H = canvas.height, cx = W/2, cy = H/2;\n const prev = new Map(graphNodes.map(n => [n.id, { x: n.x, y: n.y }]));\n graphNodes = [{ id:'brain', label:'HIVE MIND', x: cx, y: cy, r: 50, type:'brain', color:'#a855f7' }];\n (topo.agents || []).forEach((a, i, arr) => {\n const ang = (i / arr.length) * Math.PI * 2 - Math.PI / 2, r = 240;\n const p = prev.get(a.id);\n graphNodes.push({ id: a.id, label: a.displayName, x: p?.x ?? cx + Math.cos(ang) * r, y: p?.y ?? cy + Math.sin(ang) * r, targetX: cx + Math.cos(ang) * r, targetY: cy + Math.sin(ang) * r, r: a.connected ? 24 : 16, type:'agent', connected: a.connected, color: a.connected ? '#18ffff' : '#556080' });\n });\n (topo.subAgents || []).forEach((s, i, arr) => {\n const parent = graphNodes.find(n => n.id === s.parent), ang = (i / Math.max(arr.length,1)) * Math.PI * 2, r = 130;\n const p = prev.get(s.id);\n graphNodes.push({ id: s.id, label: (s.task || '').slice(0, 18), x: p?.x ?? (parent?.x ?? cx) + Math.cos(ang) * 40, y: p?.y ?? (parent?.y ?? cy) + Math.sin(ang) * 40, targetX: cx + Math.cos(ang) * r, targetY: cy + Math.sin(ang) * r, r: 12, type:'subagent', parent: s.parent, color: '#a855f7' });\n });\n graphEdges = [];\n for (const n of graphNodes) {\n if (n.type === 'agent') graphEdges.push({ from:'brain', to: n.id, active: n.connected });\n if (n.type === 'subagent') graphEdges.push({ from: n.parent ?? 'brain', to: n.id, active: true });\n }\n const now = Date.now();\n for (const e of (topo.events || [])) {\n const src = e.source ?? 'brain';\n const srcN = graphNodes.find(n => n.id === src) ?? graphNodes[0];\n const dstN = e.type === 'subagent-spawn' ? graphNodes.find(n => n.id === e.target) : graphNodes.find(n => n.id === 'brain');\n if (!srcN || !dstN) continue;\n graphSignals.push({ fromId: srcN.id, toId: dstN.id, type: e.type, color: signalColors[e.type] || '#18ffff', progress: 0, spawnedAt: now });\n }\n if (graphSignals.length > 100) graphSignals = graphSignals.slice(-100);\n document.getElementById('graph-meta').textContent = `${(topo.agents || []).length} agents \u00B7 ${(topo.subAgents || []).length} sub-agents \u00B7 ${graphSignals.length} signals`;\n}\nfunction graphLoop() {\n const c = document.getElementById('graph-canvas');\n if (!c) return;\n const ctx = c.getContext('2d'), W = c.width, H = c.height;\n const now = Date.now();\n ctx.clearRect(0, 0, W, H);\n // Ambient backdrop with a soft vignette.\n const grd = ctx.createRadialGradient(W/2, H/2, 40, W/2, H/2, Math.max(W,H)/1.4);\n grd.addColorStop(0, 'rgba(168,85,247,0.10)'); grd.addColorStop(0.5, 'rgba(24,255,255,0.03)'); grd.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = grd; ctx.fillRect(0, 0, W, H);\n\n for (const n of graphNodes) if (n.targetX != null) { n.x += (n.targetX - n.x) * 0.08; n.y += (n.targetY - n.y) * 0.08; }\n\n // \u2500\u2500 Edges: curved, with a gentle animated dash flow on active links \u2500\u2500\u2500\u2500\u2500\u2500\n for (const e of graphEdges) {\n const a = graphNodes.find(n => n.id === e.from), b = graphNodes.find(n => n.id === e.to);\n if (!a || !b) continue;\n const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;\n // Curve control point nudged perpendicular for an organic arc.\n const dx = b.x - a.x, dy = b.y - a.y, len = Math.sqrt(dx*dx + dy*dy) || 1;\n const cxp = mx - (dy / len) * 26, cyp = my + (dx / len) * 26;\n if (e.active) {\n const g = ctx.createLinearGradient(a.x, a.y, b.x, b.y);\n g.addColorStop(0, 'rgba(24,255,255,0.05)'); g.addColorStop(0.5, 'rgba(24,255,255,0.34)'); g.addColorStop(1, 'rgba(168,85,247,0.18)');\n ctx.strokeStyle = g; ctx.lineWidth = 1.6;\n ctx.setLineDash([5, 7]); ctx.lineDashOffset = -(now / 45) % 12;\n } else {\n ctx.strokeStyle = 'rgba(96,108,140,0.14)'; ctx.lineWidth = 1; ctx.setLineDash([3, 5]); ctx.lineDashOffset = 0;\n }\n ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.quadraticCurveTo(cxp, cyp, b.x, b.y); ctx.stroke();\n }\n ctx.setLineDash([]); ctx.lineDashOffset = 0;\n\n // \u2500\u2500 Signals: glowing head + fading motion trail \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n graphSignals = graphSignals.filter(s => now - s.spawnedAt < 3500);\n for (const s of graphSignals) {\n const from = graphNodes.find(n => n.id === s.fromId), to = graphNodes.find(n => n.id === s.toId);\n if (!from || !to) continue;\n s.progress = Math.min(1, (now - s.spawnedAt) / 2400);\n const t = s.progress;\n const ease = t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2;\n // Trail: several fading dots behind the head.\n for (let k = 0; k < 5; k++) {\n const tt = Math.max(0, ease - k * 0.045);\n const x = from.x + (to.x - from.x) * tt, y = from.y + (to.y - from.y) * tt;\n const alpha = (1 - k / 5) * (1 - Math.abs(t - 0.5)) * 0.9;\n ctx.globalAlpha = Math.max(0, alpha);\n ctx.fillStyle = s.color;\n ctx.beginPath(); ctx.arc(x, y, 3.2 - k * 0.4, 0, Math.PI * 2); ctx.fill();\n }\n ctx.globalAlpha = 1;\n const hx = from.x + (to.x - from.x) * ease, hy = from.y + (to.y - from.y) * ease;\n const tail = ctx.createRadialGradient(hx, hy, 0, hx, hy, 20);\n tail.addColorStop(0, s.color); tail.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = tail; ctx.beginPath(); ctx.arc(hx, hy, 18, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(hx, hy, 2.2, 0, Math.PI * 2); ctx.fill();\n }\n\n // \u2500\u2500 Nodes: layered glow, ring, core, label chip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n for (const n of graphNodes) {\n const isBrain = n.type === 'brain';\n const pulse = isBrain ? Math.sin(graphFrame / 15) * 4 : Math.sin((graphFrame + (n.x|0)) / 22) * 1.5;\n const r = n.r + pulse;\n const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.8);\n halo.addColorStop(0, n.color + '77'); halo.addColorStop(0.6, n.color + '1a'); halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.8, 0, Math.PI * 2); ctx.fill();\n // Rotating accent ring on the brain hub.\n if (isBrain) {\n ctx.save(); ctx.translate(n.x, n.y); ctx.rotate((now / 1600) % (Math.PI * 2));\n ctx.strokeStyle = n.color + '88'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 9]);\n ctx.beginPath(); ctx.arc(0, 0, r + 10, 0, Math.PI * 2); ctx.stroke();\n ctx.restore(); ctx.setLineDash([]);\n }\n const fill = ctx.createRadialGradient(n.x - r*0.3, n.y - r*0.3, r*0.2, n.x, n.y, r);\n fill.addColorStop(0, isBrain ? '#1b2142' : '#121a30'); fill.addColorStop(1, isBrain ? '#0a0e1f' : '#080c16');\n ctx.fillStyle = fill;\n ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = n.color; ctx.lineWidth = isBrain ? 2.5 : 1.8; ctx.stroke();\n ctx.fillStyle = n.color; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.34), 0, Math.PI * 2); ctx.fill();\n // Label\n ctx.fillStyle = n.color;\n ctx.font = isBrain ? '800 13px Inter, sans-serif' : '600 11px Inter, sans-serif';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText(n.label, n.x, isBrain ? n.y : n.y + r + 13);\n }\n graphFrame++;\n graphTimer = requestAnimationFrame(graphLoop);\n}\n\n// \u2500\u2500\u2500\u2500 Memory Constellation (Intelligence hub) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet constNodes = [], constEdges = [], constStats = null, constIdMap = new Map();\nlet constView = { zoom: 1, panX: 0, panY: 0 };\nlet constSim = { running: false, alpha: 1, raf: null };\nlet constFilter = '', constFocus = null, constHover = null;\nlet constDragNode = null, constPanning = false, constLoaded = false, constMoved = false;\nlet constLast = { x: 0, y: 0 };\nlet constLiveTimer = null;\nconst NODE_COLORS = { memory: '#18ffff', project: '#10e68c', agent: '#ffb53f', tag: '#a855f7' };\n\nasync function refreshIntelligence() {\n bindConstellation();\n refreshIntelAnalytics();\n if (!constLoaded) { await loadConstellation(); }\n else { sizeConstCanvas(); startConstSim(); }\n}\n\nasync function loadConstellation() {\n const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');\n applyConstellation(snap);\n constLoaded = true;\n}\n\nfunction applyConstellation(snap) {\n const cv = document.getElementById('const-canvas');\n const empty = document.getElementById('const-empty');\n constNodes = []; constEdges = []; constIdMap = new Map();\n const nodes = (snap && snap.nodes) || [];\n constStats = (snap && snap.stats) || null;\n if (empty) empty.classList.toggle('show', nodes.length === 0);\n const badge = document.getElementById('badge-intelligence');\n if (badge) badge.textContent = constStats ? constStats.memories : 0;\n if (!nodes.length) { drawConstellation(); return; }\n sizeConstCanvas();\n const W = cv ? cv.width : 800, H = cv ? cv.height : 600, cx = W / 2, cy = H / 2;\n for (let i = 0; i < nodes.length; i++) {\n const n = nodes[i];\n const ang = (i / nodes.length) * Math.PI * 2;\n const rad = 60 + Math.random() * Math.min(W, H) * 0.32;\n constNodes.push({\n id: n.id, label: n.label || '', type: n.type, score: n.score || 0,\n size: n.size || 9, degree: n.degree || 0, meta: n.meta || {},\n x: cx + Math.cos(ang) * rad, y: cy + Math.sin(ang) * rad, vx: 0, vy: 0,\n });\n }\n for (const node of constNodes) constIdMap.set(node.id, node);\n for (const e of (snap.edges || [])) {\n const sn = constIdMap.get(e.source), tn = constIdMap.get(e.target);\n if (sn && tn) constEdges.push({ sn: sn, tn: tn, w: e.weight || 1, type: e.type });\n }\n const meta = document.getElementById('const-meta');\n if (meta && constStats) meta.textContent = constStats.memories + ' mem \u00B7 ' + constStats.projects + ' proj \u00B7 ' + constStats.agents + ' agent \u00B7 ' + constStats.tags + ' tag \u00B7 ' + constStats.edges + ' links' + (constStats.droppedNodes ? ' (+' + constStats.droppedNodes + ' hidden)' : '');\n updateConstHero();\n constView = { zoom: 1, panX: 0, panY: 0 };\n constSim.alpha = 1;\n startConstSim();\n}\n\nfunction sizeConstCanvas() {\n const cv = document.getElementById('const-canvas'), wrap = document.getElementById('const-wrap');\n if (!cv || !wrap) return;\n cv.width = wrap.clientWidth || 800;\n cv.height = wrap.clientHeight || 600;\n}\n\nfunction startConstSim() {\n if (constSim.running || !constNodes.length) return;\n constSim.running = true;\n constSim.alpha = Math.max(constSim.alpha, 0.5);\n constLoop();\n}\n\nfunction constLoop() {\n const sec = document.getElementById('tab-intelligence');\n if (!sec || !sec.classList.contains('active')) { constSim.running = false; constSim.raf = null; return; }\n stepConstPhysics();\n drawConstellation();\n constSim.raf = requestAnimationFrame(constLoop);\n}\n\nfunction stepConstPhysics() {\n const cv = document.getElementById('const-canvas'); if (!cv) return;\n const W = cv.width, H = cv.height, cx = W / 2, cy = H / 2;\n const a = constSim.alpha;\n const N = constNodes.length;\n if (a > 0.01) {\n for (let i = 0; i < N; i++) {\n const ni = constNodes[i];\n for (let j = i + 1; j < N; j++) {\n const nj = constNodes[j];\n let dx = ni.x - nj.x, dy = ni.y - nj.y;\n let d2 = dx * dx + dy * dy; if (d2 < 1) d2 = 1;\n const d = Math.sqrt(d2);\n let rep = (3600 * a) / d2;\n // Hard separation: keep node circles from visually overlapping.\n const minD = ni.size + nj.size + 12;\n if (d < minD) rep += (minD - d) * 0.28 * a;\n const ux = dx / d, uy = dy / d;\n ni.vx += ux * rep; ni.vy += uy * rep;\n nj.vx -= ux * rep; nj.vy -= uy * rep;\n }\n }\n for (const e of constEdges) {\n let dx = e.tn.x - e.sn.x, dy = e.tn.y - e.sn.y;\n let d = Math.sqrt(dx * dx + dy * dy) || 1;\n const ideal = 94;\n const f = ((d - ideal) * 0.015 * a) * Math.min(2, e.w);\n const ux = dx / d, uy = dy / d;\n e.sn.vx += ux * f; e.sn.vy += uy * f;\n e.tn.vx -= ux * f; e.tn.vy -= uy * f;\n }\n for (const n of constNodes) {\n if (n === constDragNode) continue;\n n.vx += (cx - n.x) * 0.010 * a;\n n.vy += (cy - n.y) * 0.010 * a;\n n.vx *= 0.86; n.vy *= 0.86;\n const sp = Math.sqrt(n.vx * n.vx + n.vy * n.vy);\n if (sp > 20) { n.vx = n.vx / sp * 20; n.vy = n.vy / sp * 20; }\n n.x += n.vx; n.y += n.vy;\n }\n constSim.alpha *= 0.99;\n if (constSim.alpha < 0.01) constSim.alpha = 0.01;\n }\n}\n\nfunction constNodeOpacity(n) {\n if (constFilter && n.type !== constFilter) return 0.12;\n if (constFocus) {\n if (n.id === constFocus) return 1;\n const f = constIdMap.get(constFocus);\n if (f && f._neighbors && f._neighbors.has(n.id)) return 0.95;\n return 0.14;\n }\n return 1;\n}\n\nfunction drawConstellation() {\n const cv = document.getElementById('const-canvas'); if (!cv || !cv.getContext) return;\n const ctx = cv.getContext('2d'), W = cv.width, H = cv.height;\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, W, H);\n ctx.translate(constView.panX, constView.panY);\n ctx.scale(constView.zoom, constView.zoom);\n\n for (const e of constEdges) {\n const oa = Math.min(constNodeOpacity(e.sn), constNodeOpacity(e.tn));\n const lit = constFocus && (e.sn.id === constFocus || e.tn.id === constFocus);\n ctx.strokeStyle = lit ? 'rgba(24,255,255,' + (0.55 * oa) + ')' : 'rgba(120,140,180,' + (0.16 * oa) + ')';\n ctx.lineWidth = lit ? 1.6 : 0.7;\n ctx.beginPath(); ctx.moveTo(e.sn.x, e.sn.y); ctx.lineTo(e.tn.x, e.tn.y); ctx.stroke();\n }\n for (const n of constNodes) {\n const op = constNodeOpacity(n);\n const col = NODE_COLORS[n.type] || '#9aa7c0';\n const r = n.size;\n const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.4);\n halo.addColorStop(0, col + Math.round(op * 90).toString(16).padStart(2, '0'));\n halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.4, 0, Math.PI * 2); ctx.fill();\n ctx.globalAlpha = op;\n ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = col; ctx.lineWidth = (n.id === constFocus || n.id === constHover) ? 3 : 1.6;\n ctx.stroke();\n ctx.fillStyle = col; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.42), 0, Math.PI * 2); ctx.fill();\n const showLabel = r > 15 || n.id === constHover || n.id === constFocus || n.type !== 'memory';\n if (showLabel && op > 0.4) {\n ctx.globalAlpha = op;\n ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';\n ctx.font = (n.id === constFocus ? 'bold ' : '') + Math.max(10, Math.min(14, 9 + r * 0.18)) + 'px Inter, sans-serif';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n const lbl = n.label.length > 26 ? n.label.slice(0, 25) + '\u2026' : n.label;\n ctx.fillText(lbl, n.x, n.y + r + 9);\n }\n ctx.globalAlpha = 1;\n }\n}\n\nfunction constMouseWorld(e) {\n const cv = document.getElementById('const-canvas'); const rect = cv.getBoundingClientRect();\n const sx = (e.clientX - rect.left) * (cv.width / rect.width);\n const sy = (e.clientY - rect.top) * (cv.height / rect.height);\n return { sx: sx, sy: sy, wx: (sx - constView.panX) / constView.zoom, wy: (sy - constView.panY) / constView.zoom };\n}\nfunction constHitTest(wx, wy) {\n for (let i = constNodes.length - 1; i >= 0; i--) {\n const n = constNodes[i];\n const dx = wx - n.x, dy = wy - n.y;\n if (dx * dx + dy * dy <= (n.size + 4) * (n.size + 4)) return n;\n }\n return null;\n}\nfunction computeNeighbors(id) {\n const n = constIdMap.get(id); if (!n) return;\n if (n._neighbors) return;\n const set = new Set();\n for (const e of constEdges) { if (e.sn.id === id) set.add(e.tn.id); if (e.tn.id === id) set.add(e.sn.id); }\n n._neighbors = set;\n}\nfunction openConstDrawer(n) {\n const drawer = document.getElementById('const-drawer'), body = document.getElementById('const-drawer-body');\n if (!drawer || !body) return;\n const m = n.meta || {};\n let h = '<div class=\"cd-type\" style=\"color:' + (NODE_COLORS[n.type] || '#9aa7c0') + '\">' + n.type + '</div>';\n h += '<div class=\"cd-title\">' + htmlEscape(n.label) + '</div>';\n h += '<div class=\"row\"><span class=\"k\">Importance</span><span class=\"v\">' + (n.score * 100).toFixed(0) + '/100</span></div>';\n h += '<div class=\"row\"><span class=\"k\">Connections</span><span class=\"v\">' + n.degree + '</span></div>';\n if (n.type === 'memory') {\n if (m.category) h += '<div class=\"row\"><span class=\"k\">Category</span><span class=\"v\">' + htmlEscape(String(m.category)) + '</span></div>';\n if (m.agentTool) h += '<div class=\"row\"><span class=\"k\">Agent</span><span class=\"v\">' + htmlEscape(String(m.agentTool)) + '</span></div>';\n if (m.projectName) h += '<div class=\"row\"><span class=\"k\">Project</span><span class=\"v\">' + htmlEscape(String(m.projectName)) + '</span></div>';\n if (m.content) h += '<div class=\"cd-section\"><div class=\"cd-label\">Content</div><div class=\"cd-content\">' + htmlEscape(String(m.content)) + '</div></div>';\n const tags = Array.isArray(m.tags) ? m.tags : [];\n if (tags.length) h += '<div class=\"cd-section\"><div class=\"cd-label\">Tags</div><div class=\"tag-cloud\">' + tags.map(function (t) { return '<span class=\"tc\">#' + htmlEscape(String(t)) + '</span>'; }).join('') + '</div></div>';\n const src = Array.isArray(m.sources) ? m.sources : [];\n if (src.length) h += '<div class=\"cd-section\"><div class=\"cd-label\">Sources</div><div class=\"cd-content mono\">' + src.map(function (s) { return htmlEscape(String(s)); }).join('<br>') + '</div></div>';\n } else {\n h += '<div class=\"cd-section\"><div class=\"cd-content\">This ' + n.type + ' node links ' + n.degree + ' memories in the constellation. Click connected nodes to explore.</div></div>';\n }\n body.innerHTML = h;\n drawer.classList.add('open');\n}\nfunction closeConstDrawer() {\n const drawer = document.getElementById('const-drawer'); if (drawer) drawer.classList.remove('open');\n constFocus = null;\n}\n\nfunction bindConstellation() {\n const wrap = document.getElementById('const-wrap'); if (!wrap || wrap._bound) return;\n wrap._bound = true;\n const cv = document.getElementById('const-canvas');\n wrap.addEventListener('wheel', function (e) {\n e.preventDefault();\n const p = constMouseWorld(e);\n const factor = e.deltaY < 0 ? 1.12 : 0.89;\n const nz = Math.max(0.25, Math.min(4, constView.zoom * factor));\n constView.panX = p.sx - p.wx * nz;\n constView.panY = p.sy - p.wy * nz;\n constView.zoom = nz;\n if (!constSim.running) drawConstellation();\n }, { passive: false });\n wrap.addEventListener('mousedown', function (e) {\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n constMoved = false; constLast = { x: e.clientX, y: e.clientY };\n if (hit) { constDragNode = hit; constSim.alpha = Math.max(constSim.alpha, 0.4); startConstSim(); }\n else { constPanning = true; wrap.classList.add('dragging'); }\n });\n window.addEventListener('mousemove', function (e) {\n if (constDragNode) {\n const p = constMouseWorld(e); constDragNode.x = p.wx; constDragNode.y = p.wy; constDragNode.vx = 0; constDragNode.vy = 0; constMoved = true;\n return;\n }\n if (constPanning) {\n constView.panX += e.clientX - constLast.x; constView.panY += e.clientY - constLast.y;\n constLast = { x: e.clientX, y: e.clientY }; constMoved = true;\n if (!constSim.running) drawConstellation();\n return;\n }\n const sec = document.getElementById('tab-intelligence');\n if (!sec || !sec.classList.contains('active')) return;\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n const tip = document.getElementById('const-tip');\n constHover = hit ? hit.id : null;\n if (hit && tip) {\n tip.innerHTML = '<b style=\"color:' + (NODE_COLORS[hit.type] || '#fff') + '\">' + htmlEscape(hit.label) + '</b><br>' + hit.type + ' \u00B7 ' + (hit.score * 100).toFixed(0) + '/100 \u00B7 ' + hit.degree + ' links';\n const rect = cv.getBoundingClientRect();\n tip.style.left = (e.clientX - rect.left) + 'px'; tip.style.top = (e.clientY - rect.top) + 'px';\n tip.classList.add('show');\n } else if (tip) tip.classList.remove('show');\n });\n window.addEventListener('mouseup', function (e) {\n if (constDragNode && !constMoved) { constFocus = constDragNode.id; computeNeighbors(constFocus); openConstDrawer(constDragNode); }\n else if (constPanning && !constMoved) {\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n if (!hit) { constFocus = null; closeConstDrawer(); }\n }\n constDragNode = null; constPanning = false; wrap.classList.remove('dragging');\n });\n}\nfunction setConstFilter(btn, type) {\n constFilter = type;\n const chips = document.querySelectorAll('#const-chips .chip');\n chips.forEach(function (c) { c.classList.toggle('active', c === btn); });\n if (!constSim.running) drawConstellation();\n}\nfunction highlightConstSearch() {\n const q = (document.getElementById('const-search').value || '').toLowerCase().trim();\n if (!q) { constFocus = null; if (!constSim.running) drawConstellation(); return; }\n const hit = constNodes.find(function (n) { return n.label.toLowerCase().includes(q); });\n if (hit) { constFocus = hit.id; computeNeighbors(hit.id); openConstDrawer(hit); }\n if (!constSim.running) drawConstellation();\n}\nfunction rebuildConstellation() { constLoaded = false; toast('Rebuilding', 'Recomputing the memory constellation\u2026', 'info'); loadConstellation(); }\nfunction resetConstellationView() { constView = { zoom: 1, panX: 0, panY: 0 }; constFocus = null; closeConstDrawer(); constSim.alpha = 0.6; startConstSim(); }\nfunction constLiveBump() {\n if (!constLoaded) return;\n constSim.alpha = Math.max(constSim.alpha, 0.5); startConstSim();\n if (constLiveTimer) clearTimeout(constLiveTimer);\n constLiveTimer = setTimeout(function () { constLoaded = false; loadConstellation(); }, 4000);\n}\n\n// \u2500\u2500\u2500\u2500 Intelligence analytics strip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshIntelAnalytics() {\n const d = await api('/api/v6/memory-intelligence');\n if (!d) return;\n animateNum('intel-total', d.totalEntries || 0);\n const cov = Math.round((d.sourceCoverage || 0) * 100);\n const covEl = document.getElementById('intel-coverage'); if (covEl) covEl.textContent = cov + '%';\n const covM = document.getElementById('intel-coverage-meter'); if (covM) covM.style.width = cov + '%';\n animateNum('intel-recent', d.recent24h || 0);\n animateNum('intel-highimp', d.highImportance || 0);\n drawIntelAgents(d.byAgent || []);\n drawIntelCats(d.byCategory || []);\n renderTagCloud(d.topTags || []);\n renderIntelRecent(d.recent || []);\n}\nfunction drawIntelAgents(rows) {\n const cv = document.getElementById('chart-intel-agents'); if (!cv || !window.Chart) return;\n const t = chartTheme(); const top = rows.slice(0, 6);\n if (charts.intelAgents) charts.intelAgents.destroy();\n charts.intelAgents = new Chart(cv, { type: 'bar', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: '#18ffff', borderRadius: 5 }] }, options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { grid: { display: false }, ticks: { color: t.text } } } } });\n}\nfunction drawIntelCats(rows) {\n const cv = document.getElementById('chart-intel-cats'); if (!cv || !window.Chart) return;\n const t = chartTheme(); const top = rows.slice(0, 6);\n if (charts.intelCats) charts.intelCats.destroy();\n charts.intelCats = new Chart(cv, { type: 'doughnut', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: ['#18ffff', '#a855f7', '#ec4899', '#10e68c', '#ffb53f', '#4fb6ff'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: t.text, boxWidth: 12 } } } } });\n}\nfunction renderTagCloud(rows) {\n const el = document.getElementById('intel-tags'); if (!el) return;\n el.innerHTML = rows.length ? rows.slice(0, 18).map(function (r) { return '<span class=\"tc\">#' + htmlEscape(r.name) + ' <b>' + r.count + '</b></span>'; }).join('') : '<div class=\"empty-tight\">No tags yet.</div>';\n}\nfunction renderIntelRecent(rows) {\n const el = document.getElementById('intel-recent-list'); if (!el) return;\n el.innerHTML = rows.length ? rows.slice(0, 10).map(function (e) { return '<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill purple\">' + htmlEscape(e.agentTool || 'agent') + '</span><span>' + htmlEscape(e.category || '') + '</span></div><div class=\"content\">' + htmlEscape(String(e.content || '').slice(0, 160)) + '</div></div>'; }).join('') : '<div class=\"empty-tight\">No memories yet.</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Overview constellation hero \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction updateConstHero() {\n if (!constStats) return;\n const nodes = constStats.memories + constStats.projects + constStats.agents + constStats.tags;\n const n = document.getElementById('hero-const-nodes'); if (n) n.textContent = nodes;\n const e = document.getElementById('hero-const-edges'); if (e) e.textContent = constStats.edges;\n const m = document.getElementById('hero-const-mem'); if (m) m.textContent = constStats.memories;\n}\nasync function loadConstHeroStats() {\n if (constStats) { updateConstHero(); return; }\n const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');\n if (snap && snap.stats) { constStats = snap.stats; updateConstHero(); }\n}\n\n// \u2500\u2500\u2500\u2500 Command Palette \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst commands = [\n ...FEATURES.map(([id, name, desc]) => ({ type: 'nav', label: name, target: id, hint: desc || 'Go to' })),\n { type: 'action', label: 'Run Reflections now', fn: 'runDream' },\n { type: 'action', label: 'Clean up memory now', fn: 'runForget' },\n { type: 'action', label: 'Toggle dark / light theme', fn: 'toggleTheme' },\n { type: 'action', label: 'Connect all tools', fn: 'attachAll' },\n { type: 'action', label: 'Refresh everything', fn: 'refresh' },\n];\nlet cmdSelected = 0;\nfunction openCmdPalette() {\n document.getElementById('cmd-overlay').classList.add('open');\n const input = document.getElementById('cmd-input');\n input.value = ''; cmdSelected = 0;\n renderCmdResults(commands);\n setTimeout(() => input.focus(), 50);\n}\nfunction closeCmdPalette() { document.getElementById('cmd-overlay').classList.remove('open'); }\nfunction renderCmdResults(items) {\n document.getElementById('cmd-results').innerHTML = items.map((c, i) => `<div class=\"cmd-result ${i === cmdSelected ? 'selected' : ''}\" onclick=\"runCmd(${i})\" data-idx=\"${i}\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${c.type === 'nav' ? '<line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>' : '<circle cx=\"12\" cy=\"12\" r=\"10\"/><polygon points=\"10 8 16 12 10 16 10 8\"/>'}</svg><div class=\"meta\">${c.label}</div><span class=\"hint\">${c.hint || c.type}</span></div>`).join('');\n}\nfunction runCmd(i) {\n const filtered = filterCmds(document.getElementById('cmd-input').value);\n const c = filtered[i];\n if (!c) return;\n closeCmdPalette();\n if (c.type === 'nav') switchTab(c.target, '');\n else if (c.type === 'action' && typeof window[c.fn] === 'function') window[c.fn]();\n}\nfunction filterCmds(q) {\n if (!q) return commands;\n const l = q.toLowerCase();\n return commands.filter(c => c.label.toLowerCase().includes(l));\n}\ndocument.getElementById('cmd-input').addEventListener('input', e => { cmdSelected = 0; renderCmdResults(filterCmds(e.target.value)); });\ndocument.getElementById('cmd-input').addEventListener('keydown', e => {\n const filtered = filterCmds(e.target.value);\n if (e.key === 'ArrowDown') { e.preventDefault(); cmdSelected = Math.min(cmdSelected + 1, filtered.length - 1); renderCmdResults(filtered); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); cmdSelected = Math.max(cmdSelected - 1, 0); renderCmdResults(filtered); }\n else if (e.key === 'Enter') { e.preventDefault(); runCmd(cmdSelected); }\n else if (e.key === 'Escape') closeCmdPalette();\n});\ndocument.addEventListener('keydown', e => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); openCmdPalette(); } });\n\n// \u2500\u2500\u2500\u2500 Live log / ticker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst logEntries = [];\nfunction appendLog(source, type, detail) {\n logEntries.unshift({ t: new Date(), source, type, detail: String(detail || '').slice(0, 320) });\n if (logEntries.length > 200) logEntries.length = 200;\n renderTicker(); renderLogPanel(); morphBrainState(type);\n}\nfunction renderTicker() {\n const el = document.getElementById('log-scroll');\n if (!el) return;\n const recent = logEntries.slice(0, 6).map(e => `${e.t.toLocaleTimeString()} \u00B7 ${e.source}/${e.type} \u00B7 ${e.detail.slice(0, 60)}`).join(' || ');\n el.textContent = recent || 'Awaiting activity\u2026';\n}\nfunction renderLogPanel() {\n const panel = document.getElementById('log-panel-list');\n if (!panel) return;\n panel.innerHTML = logEntries.slice(0, 80).map(e => `<div class=\"log-row\"><span class=\"t\">${htmlEscape(e.t.toLocaleTimeString())}</span><span class=\"src\">${htmlEscape(e.source)}</span><span class=\"type\">${htmlEscape(e.type)}</span><span>${htmlEscape(e.detail)}</span></div>`).join('');\n}\nfunction toggleLogPanel() { document.getElementById('log-panel').classList.toggle('open'); if (document.getElementById('log-panel').classList.contains('open')) renderLogPanel(); }\nfunction clearLogs() { logEntries.length = 0; renderTicker(); renderLogPanel(); }\nconst BRAIN_STATES = { 'memory-write':'learning \u00B7 writing memory','subagent-spawn':'sub-agent dispatched','dream':'dreaming \u00B7 reflecting','revisit':'revisiting recent','counterfactual':'running counterfactual','consolidation':'consolidating','contradiction':'resolving conflict','quarantine':'isolating suspect','collision':'arbitrating collision','firewall-block':'blocking risky action' };\nlet stateTimer = null;\nfunction morphBrainState(type) {\n const el = document.getElementById('brain-state');\n el.textContent = BRAIN_STATES[type] || 'learning \u00B7 reflecting \u00B7 charging';\n if (stateTimer) clearTimeout(stateTimer);\n stateTimer = setTimeout(() => { el.textContent = 'learning \u00B7 reflecting \u00B7 charging'; }, 6000);\n}\nasync function pullRecentActivity() {\n // Reconciliation safety net: pick up anything the WebSocket missed\n // (e.g. a dropped frame). ingestActivity dedups, so live events that\n // already arrived over the socket are ignored here.\n const log = await api('/api/v6/activity-log?limit=60');\n if (!Array.isArray(log)) return;\n for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n}\n\n// \u2500\u2500\u2500\u2500 WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction connectWS() {\n const proto = location.protocol === 'https:' ? 'wss' : 'ws';\n ws = new WebSocket(`${proto}://${location.host}`);\n ws.onopen = () => { appendLog('ws','connected','dashboard WebSocket online'); setConn(true); toast('Connected', 'live updates enabled', 'success'); };\n ws.onmessage = e => { try { const m = JSON.parse(e.data);\n if (m.type === 'activity' && m.event) {\n if (ingestActivity(m.event, true)) {\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n if (m.event.type === 'memory-write' && document.getElementById('tab-intelligence')?.classList.contains('active')) constLiveBump();\n }\n return;\n }\n if (m.type === 'activity-backlog' && Array.isArray(m.events)) {\n for (let i = m.events.length - 1; i >= 0; i--) ingestActivity(m.events[i], false);\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n return;\n }\n if (m.type === 'insights' || m.type === 'modules') refresh();\n if (m.type === 'insights' && m.insights) for (const ins of m.insights.slice(0,5)) appendLog(ins.sourceAgent || 'brain','insight',ins.title || ins.content);\n if (m.type === 'injection') appendLog(m.adapter || 'agent','injection', m.insightTitle || 'context injected');\n if (m.type === 'info') appendLog('brain','info', m.message);\n if (m.type === 'error') { appendLog('brain','error', m.error); toast('Brain error', m.error, 'error'); }\n if (m.type === 'agents-detected') appendLog('brain','agents', (m.agents || []).join(', '));\n } catch {} };\n ws.onclose = () => { appendLog('ws','disconnected','reconnecting'); setConn(false); setTimeout(connectWS, 2000); };\n ws.onerror = () => { setConn(false); };\n}\n\n// \u2500\u2500\u2500\u2500 Bootstrapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconnectWS();\nrefresh();\npullRecentActivity();\nsetInterval(refresh, 5000); // dashboard cards heartbeat\nsetInterval(pullRecentActivity, 15000); // reconciliation only \u2014 live events arrive via WebSocket\nsetInterval(renderSparkline, 10000); // keep the events/min window current\nif (window.lucide) try { window.lucide.createIcons(); } catch {}\n\n// Welcome toast\nsetTimeout(() => toast('Welcome to Shadow Brain', 'Showing the essentials. Flip to Advanced (top-left) for every feature, or press \u2318K to search.', 'info'), 800);\n</script>\n</body>\n</html>";
|
|
1
|
+
export declare const V6_DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\" data-navmode=\"simple\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=5.0\">\n<meta name=\"color-scheme\" content=\"dark light\">\n<title>Shadow Brain v7.2 \u2014 Hive Mind Control</title>\n<link rel=\"icon\" type=\"image/svg+xml\" href=\"data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='22' fill='%230a0e1a'/><path d='M50 50 L24 30 M50 50 L76 30 M50 50 L24 70 M50 50 L76 70' stroke='%23ffffff' stroke-opacity='0.28' stroke-width='3'/><circle cx='50' cy='50' r='12' fill='%2318ffff'/><circle cx='24' cy='30' r='7' fill='%2310e68c'/><circle cx='76' cy='30' r='7' fill='%23ffb53f'/><circle cx='24' cy='70' r='7' fill='%23a855f7'/><circle cx='76' cy='70' r='7' fill='%23ec4899'/></svg>\">\n<link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n<script src=\"https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js\"></script>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js\"></script>\n<script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.min.js\"></script>\n<style>\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Design System Tokens */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n :root {\n /* Dark theme */\n --bg: #030509;\n --bg-elev-1: #090d17;\n --bg-elev-2: #10152a;\n --bg-elev-3: #18203b;\n --bg-glass: rgba(15, 20, 38, 0.55);\n --bg-glass-strong: rgba(22, 28, 52, 0.78);\n --border: rgba(255, 255, 255, 0.08);\n --border-strong: rgba(255, 255, 255, 0.14);\n --border-accent: rgba(24, 255, 255, 0.32);\n\n --text: #e8ecf4;\n --text-2: #a4afc4;\n --text-3: #64708a;\n --text-dim: #3e4762;\n\n --brand-cyan: #18ffff;\n --brand-purple: #a855f7;\n --brand-pink: #ec4899;\n --brand-gradient: linear-gradient(135deg, #18ffff 0%, #a855f7 50%, #ec4899 100%);\n --brand-gradient-soft: linear-gradient(135deg, rgba(24,255,255,0.15), rgba(168,85,247,0.15));\n\n --success: #10e68c;\n --warning: #ffb53f;\n --danger: #ff5864;\n --info: #4fb6ff;\n\n --radius-sm: 6px;\n --radius: 10px;\n --radius-lg: 14px;\n --radius-xl: 20px;\n\n --shadow-sm: 0 1px 2px rgba(0,0,0,0.3);\n --shadow: 0 6px 24px -6px rgba(0,0,0,0.5), 0 0 0 1px var(--border);\n --shadow-lg: 0 18px 56px -12px rgba(0,0,0,0.6), 0 0 0 1px var(--border-strong);\n --shadow-glow: 0 0 40px rgba(24,255,255,0.15);\n --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0) 42%);\n\n --duration-fast: 120ms;\n --duration: 220ms;\n --duration-slow: 400ms;\n --easing: cubic-bezier(0.4, 0, 0.2, 1);\n --easing-spring: cubic-bezier(0.34, 1.56, 0.64, 1);\n\n --sidebar-w: 252px;\n --header-h: 64px;\n --ticker-h: 32px;\n\n --font-sans: 'Inter', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif;\n --font-mono: 'JetBrains Mono', 'SF Mono', Menlo, Consolas, monospace;\n }\n\n /* Light theme \u2014 clean, high-contrast, professional */\n html[data-theme=\"light\"] {\n --bg: #f7f8fb;\n --bg-elev-1: #ffffff;\n --bg-elev-2: #f2f4f8;\n --bg-elev-3: #e8ecf2;\n --bg-glass: rgba(255, 255, 255, 0.88);\n --bg-glass-strong: rgba(255, 255, 255, 0.96);\n --border: rgba(15, 23, 42, 0.10);\n --border-strong: rgba(15, 23, 42, 0.18);\n --border-accent: rgba(99, 102, 241, 0.38);\n --text: #0a0f1c;\n --text-2: #2b3648;\n --text-3: #55607a;\n --text-dim: #9ca3af;\n --brand-cyan: #0891b2;\n --brand-purple: #7c3aed;\n --brand-pink: #db2777;\n --brand-gradient: linear-gradient(135deg, #0891b2 0%, #7c3aed 55%, #db2777 100%);\n --brand-gradient-soft: linear-gradient(135deg, rgba(8,145,178,0.10), rgba(124,58,237,0.10));\n --success: #059669;\n --warning: #d97706;\n --danger: #dc2626;\n --info: #2563eb;\n --shadow-sm: 0 1px 2px rgba(15,23,42,0.06);\n --shadow: 0 6px 22px -6px rgba(15,23,42,0.14), 0 0 0 1px var(--border);\n --shadow-lg: 0 18px 50px -12px rgba(15,23,42,0.2), 0 0 0 1px var(--border-strong);\n --shadow-glow: 0 0 32px rgba(8,145,178,0.12);\n --card-sheen: linear-gradient(180deg, rgba(255,255,255,0.5), rgba(255,255,255,0) 42%);\n }\n html[data-theme=\"light\"] body::before {\n background:\n radial-gradient(ellipse at 10% 0%, rgba(124,58,237,0.06) 0%, transparent 45%),\n radial-gradient(ellipse at 90% 100%, rgba(8,145,178,0.05) 0%, transparent 50%);\n }\n html[data-theme=\"light\"] ::-webkit-scrollbar-thumb { background: rgba(15,23,42,0.15); }\n html[data-theme=\"light\"] ::-webkit-scrollbar-thumb:hover { background: rgba(124,58,237,0.35); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Reset + base */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n html, body {\n height: 100%;\n font-family: var(--font-sans);\n font-size: 14px;\n background: var(--bg);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n line-height: 1.5;\n overflow: hidden;\n }\n body { display: flex; flex-direction: column; }\n a { color: var(--brand-cyan); text-decoration: none; }\n a:hover { text-decoration: underline; }\n button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }\n input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }\n\n /* Ambient backdrop */\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n pointer-events: none;\n background:\n radial-gradient(ellipse at 10% 0%, rgba(168,85,247,0.10) 0%, transparent 45%),\n radial-gradient(ellipse at 90% 100%, rgba(24,255,255,0.08) 0%, transparent 50%),\n radial-gradient(ellipse at 50% 50%, rgba(236,72,153,0.04) 0%, transparent 60%);\n z-index: 0;\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Scrollbars */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n ::-webkit-scrollbar { width: 8px; height: 8px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 4px; }\n ::-webkit-scrollbar-thumb:hover { background: rgba(168,85,247,0.4); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Header */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n header {\n height: var(--header-h);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px) saturate(180%);\n -webkit-backdrop-filter: blur(24px) saturate(180%);\n border-bottom: 1px solid var(--border);\n display: flex;\n align-items: center;\n gap: 16px;\n padding: 0 20px;\n position: relative;\n z-index: 100;\n flex-shrink: 0;\n }\n .brain-hero {\n position: relative;\n width: 44px; height: 44px;\n flex-shrink: 0;\n }\n .brain-hero .aura {\n position: absolute; inset: -8px;\n border-radius: 50%;\n background: radial-gradient(circle, rgba(168,85,247,0.55) 0%, rgba(24,255,255,0.15) 40%, transparent 75%);\n animation: auraPulse 3.2s ease-in-out infinite;\n filter: blur(6px);\n }\n @keyframes auraPulse { 50% { transform: scale(1.22); opacity: 1; } }\n .brain-hero svg { position: relative; width: 100%; height: 100%; filter: drop-shadow(0 0 8px rgba(24,255,255,0.4)); }\n .brain-hero .hemi { fill: none; stroke: var(--brand-cyan); stroke-width: 1.4; stroke-linecap: round; opacity: 0.9; }\n .brain-hero .hemi-r { stroke: var(--brand-purple); }\n .brain-hero .synapse { animation: synapse 1.4s ease-in-out infinite; transform-origin: center; transform-box: fill-box; }\n @keyframes synapse { 0%, 100% { opacity: 0.3; } 50% { opacity: 1; } }\n .brain-hero .charge { stroke-dasharray: 2 6; animation: rot 2s linear infinite; }\n @keyframes rot { to { stroke-dashoffset: -20; } }\n\n .brand {\n display: flex; flex-direction: column; line-height: 1.15;\n }\n .brand .name {\n font-size: 15px; font-weight: 800; letter-spacing: -0.2px;\n background: var(--brand-gradient);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n }\n .brand .state {\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--brand-cyan);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n display: flex; align-items: center; gap: 5px;\n }\n .brand .state::before {\n content: \"\"; width: 5px; height: 5px; border-radius: 50%;\n background: var(--success); box-shadow: 0 0 6px var(--success);\n animation: pulse 1.5s infinite;\n }\n @keyframes pulse { 50% { opacity: 0.5; } }\n\n .header-stats {\n display: flex; gap: 8px; margin-left: 16px;\n flex-wrap: nowrap; overflow-x: auto;\n scrollbar-width: none;\n }\n .header-stats::-webkit-scrollbar { display: none; }\n .stat-pill {\n display: flex; align-items: center; gap: 6px;\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 999px;\n padding: 5px 12px;\n font-size: 11px;\n color: var(--text-2);\n white-space: nowrap;\n transition: all var(--duration) var(--easing);\n }\n .stat-pill:hover { border-color: var(--border-strong); color: var(--text); }\n .stat-pill .n { font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }\n\n .header-right {\n margin-left: auto;\n display: flex; gap: 8px; align-items: center;\n }\n .icon-btn {\n width: 34px; height: 34px;\n display: flex; align-items: center; justify-content: center;\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 10px;\n color: var(--text-2);\n transition: all var(--duration) var(--easing);\n }\n .icon-btn:hover { background: var(--bg-elev-3); color: var(--text); border-color: var(--border-strong); }\n .icon-btn svg { width: 16px; height: 16px; }\n .kbd-hint {\n background: var(--bg-glass);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 5px 10px;\n font-family: var(--font-mono); font-size: 10px;\n color: var(--text-3);\n display: flex; align-items: center; gap: 6px;\n transition: all var(--duration);\n }\n .kbd-hint:hover { color: var(--text); border-color: var(--brand-cyan); }\n kbd {\n background: var(--bg-elev-3);\n border-radius: 4px;\n padding: 1px 5px;\n font-size: 9px;\n font-family: var(--font-mono);\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Layout: sidebar + main */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .layout {\n display: grid;\n grid-template-columns: var(--sidebar-w) 1fr;\n flex: 1;\n overflow: hidden;\n position: relative;\n z-index: 1;\n }\n\n aside.sidebar {\n background: var(--bg-glass);\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n border-right: 1px solid var(--border);\n overflow-y: auto;\n padding: 16px 10px;\n }\n .nav-group { margin-bottom: 14px; }\n .nav-group-label {\n font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;\n color: var(--text-3); padding: 6px 12px; font-weight: 700;\n }\n /* Simple / Advanced mode toggle */\n .mode-toggle {\n display: flex; gap: 3px; padding: 3px;\n background: var(--bg-elev-1); border: 1px solid var(--border);\n border-radius: 999px; margin: 0 6px 14px;\n }\n .mode-btn {\n flex: 1; padding: 6px 10px; border-radius: 999px;\n font-size: 11.5px; font-weight: 700; color: var(--text-3);\n transition: all var(--duration-fast) var(--easing);\n }\n .mode-btn:hover { color: var(--text-2); }\n .mode-btn.active { background: var(--brand-gradient); color: #fff; box-shadow: 0 2px 10px -2px rgba(168,85,247,0.5); }\n /* In simple mode, advanced groups are hidden entirely. */\n html[data-navmode=\"simple\"] [data-adv] { display: none; }\n .nav-item {\n display: flex; align-items: center; gap: 10px;\n padding: 8px 12px;\n border-radius: 9px;\n cursor: pointer;\n font-size: 12.5px;\n color: var(--text-2);\n transition: background var(--duration-fast) var(--easing), color var(--duration-fast) var(--easing), transform var(--duration-fast) var(--easing);\n position: relative;\n width: 100%; text-align: left;\n font-weight: 500;\n }\n .nav-item:hover { background: var(--bg-elev-2); color: var(--text); transform: translateX(2px); }\n .nav-item.active {\n background: var(--brand-gradient-soft);\n color: var(--text);\n font-weight: 600;\n }\n .nav-item.active::before {\n content: \"\";\n position: absolute; left: 0; top: 5px; bottom: 5px;\n width: 3px; border-radius: 0 3px 3px 0;\n background: var(--brand-gradient);\n box-shadow: 0 0 10px var(--brand-cyan);\n }\n .nav-item svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--text-3); transition: color var(--duration-fast); }\n .nav-item:hover svg, .nav-item.active svg { color: var(--brand-cyan); }\n .nav-item .badge-count {\n margin-left: auto;\n background: var(--bg-elev-3);\n border-radius: 9px;\n padding: 0px 7px;\n font-size: 10px;\n color: var(--text-3);\n font-variant-numeric: tabular-nums;\n }\n .nav-item.active .badge-count { background: var(--brand-cyan); color: #000; }\n\n main {\n overflow-y: auto;\n padding: 20px 26px 48px;\n position: relative;\n }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Sections */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .section { display: none; }\n .section.active { display: block; animation: sectionIn var(--duration-slow) var(--easing); }\n @keyframes sectionIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }\n\n .section-header {\n display: flex; align-items: flex-end; justify-content: space-between;\n margin-bottom: 18px; flex-wrap: wrap; gap: 12px;\n }\n .section-title {\n font-size: 22px; font-weight: 800; letter-spacing: -0.5px;\n display: flex; align-items: center; gap: 10px;\n }\n .section-title svg { color: var(--brand-cyan); width: 22px; height: 22px; }\n .section-subtitle {\n color: var(--text-3); font-size: 12.5px; margin-top: 4px;\n }\n .section-actions { display: flex; gap: 8px; flex-wrap: wrap; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Cards */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 14px;\n }\n .grid.cols-2 { grid-template-columns: repeat(2, 1fr); }\n .grid.cols-3 { grid-template-columns: repeat(3, 1fr); }\n .grid.cols-4 { grid-template-columns: repeat(4, 1fr); }\n\n .card {\n background: var(--bg-glass);\n backdrop-filter: blur(20px) saturate(150%);\n -webkit-backdrop-filter: blur(20px) saturate(150%);\n border: 1px solid var(--border);\n border-radius: var(--radius-lg);\n padding: 18px;\n transition: transform var(--duration) var(--easing), box-shadow var(--duration) var(--easing), border-color var(--duration) var(--easing);\n position: relative;\n overflow: hidden;\n box-shadow: var(--shadow-sm);\n }\n .card::after {\n content: \"\";\n position: absolute; inset: 0 0 auto 0; height: 50%;\n background: var(--card-sheen);\n pointer-events: none; border-radius: var(--radius-lg) var(--radius-lg) 0 0;\n }\n .card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow); }\n .card.span-2 { grid-column: span 2; }\n .card.span-full { grid-column: 1 / -1; }\n .card.accent::before {\n content: \"\";\n position: absolute; inset: 0; pointer-events: none;\n background: linear-gradient(135deg, rgba(24,255,255,0.08), transparent 50%);\n }\n /* Canvas-bearing cards (constellation, charts, graphs) skip the top sheen so\n visualizations stay crisp \u2014 especially in light theme. */\n .card:has(canvas)::after { display: none; }\n .card-header {\n display: flex; align-items: center; justify-content: space-between;\n margin-bottom: 14px; gap: 8px;\n }\n .card-title {\n font-size: 13px; font-weight: 700; letter-spacing: 0.01em;\n display: flex; align-items: center; gap: 8px;\n }\n .card-title svg { width: 14px; height: 14px; color: var(--brand-cyan); }\n\n .stat-card { padding: 18px; }\n .stat-card .label { font-size: 11px; color: var(--text-3); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 700; }\n .stat-card .value { font-size: 32px; font-weight: 900; letter-spacing: -1px; margin: 6px 0 4px; font-variant-numeric: tabular-nums; }\n .stat-card .value.accent { background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }\n .stat-card .trend { font-size: 11px; color: var(--text-2); display: flex; align-items: center; gap: 4px; }\n .stat-card .trend.up { color: var(--success); }\n .stat-card .trend.down { color: var(--danger); }\n\n .workflow-strip {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 12px;\n align-items: end;\n margin-bottom: 16px;\n }\n .workflow-path {\n font-family: var(--font-mono);\n font-size: 12px;\n color: var(--text-2);\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 10px 12px;\n width: 100%;\n }\n .warning-stack { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }\n .warning-banner {\n display: flex;\n gap: 10px;\n align-items: flex-start;\n border: 1px solid rgba(217, 119, 6, 0.38);\n background: rgba(217, 119, 6, 0.10);\n color: var(--warning);\n border-radius: 8px;\n padding: 10px 12px;\n font-size: 12px;\n }\n .brain-feed { display: flex; flex-direction: column; gap: 10px; }\n .stack { display: flex; flex-direction: column; gap: 8px; }\n .memory-item {\n border: 1px solid var(--border);\n background: var(--bg-elev-1);\n border-radius: 8px;\n padding: 10px 12px;\n }\n .memory-item .meta {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n align-items: center;\n margin-bottom: 6px;\n font-size: 11px;\n color: var(--text-3);\n }\n .memory-item .content {\n color: var(--text-2);\n font-size: 12px;\n line-height: 1.45;\n word-break: break-word;\n }\n .ops-agent-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n gap: 8px;\n }\n .ops-agent {\n border: 1px solid var(--border);\n background: var(--bg-elev-1);\n border-radius: 8px;\n padding: 9px 10px;\n font-size: 12px;\n }\n .ops-agent .name { font-weight: 700; margin-bottom: 5px; }\n .ops-agent .status { display: flex; gap: 6px; flex-wrap: wrap; }\n .empty-tight { color: var(--text-3); padding: 18px 4px; font-size: 12px; text-align: center; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Rows, pills, badges */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .row {\n display: flex; justify-content: space-between; align-items: center;\n padding: 9px 6px; border-bottom: 1px solid var(--border);\n gap: 8px; border-radius: 6px;\n transition: background var(--duration-fast) var(--easing);\n }\n .row:hover { background: var(--bg-elev-2); }\n .row:last-child { border: none; }\n .row .k { font-size: 12px; color: var(--text-2); }\n .row .v { font-size: 12.5px; font-weight: 700; font-variant-numeric: tabular-nums; }\n .row .v.good { color: var(--success); }\n .row .v.warn { color: var(--warning); }\n .row .v.bad { color: var(--danger); }\n\n .pill {\n display: inline-flex; align-items: center; gap: 4px;\n padding: 3px 9px; border-radius: 999px;\n background: var(--bg-elev-3); border: 1px solid var(--border);\n font-size: 10px; font-weight: 700; letter-spacing: 0.02em;\n color: var(--text-2);\n }\n .pill.green { background: rgba(16,230,140,.12); color: var(--success); border-color: rgba(16,230,140,.3); }\n .pill.red { background: rgba(255,88,100,.12); color: var(--danger); border-color: rgba(255,88,100,.3); }\n .pill.yellow { background: rgba(255,181,63,.12); color: var(--warning); border-color: rgba(255,181,63,.3); }\n .pill.purple { background: rgba(168,85,247,.15); color: var(--brand-purple); border-color: rgba(168,85,247,.3); }\n .pill.cyan { background: rgba(24,255,255,.12); color: var(--brand-cyan); border-color: rgba(24,255,255,.3); }\n .pill.pink { background: rgba(236,72,153,.12); color: var(--brand-pink); border-color: rgba(236,72,153,.3); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Buttons */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .btn {\n display: inline-flex; align-items: center; gap: 6px;\n padding: 8px 14px;\n background: var(--bg-elev-2);\n border: 1px solid var(--border);\n border-radius: 9px;\n color: var(--text);\n font-size: 12.5px;\n font-weight: 600;\n transition: transform var(--duration-fast) var(--easing), box-shadow var(--duration) var(--easing), background var(--duration) var(--easing), border-color var(--duration) var(--easing), filter var(--duration) var(--easing);\n white-space: nowrap;\n box-shadow: var(--shadow-sm);\n }\n .btn:hover { background: var(--bg-elev-3); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow); }\n .btn:active { transform: translateY(0) scale(0.98); }\n .btn svg { width: 13px; height: 13px; }\n .btn.sm { padding: 4px 10px; font-size: 11px; border-radius: 7px; }\n .btn.lg { padding: 11px 18px; font-size: 13px; border-radius: 11px; }\n .btn.primary {\n background: var(--brand-gradient);\n color: #fff;\n border-color: transparent;\n box-shadow: 0 4px 14px -4px rgba(168,85,247,0.5);\n }\n .btn.primary:hover { filter: brightness(1.08); transform: translateY(-1px); box-shadow: 0 8px 24px -4px rgba(24,255,255,0.4); }\n .btn.ghost { background: transparent; }\n .btn.ghost:hover { background: var(--bg-elev-2); }\n .btn.danger { color: var(--danger); border-color: rgba(255,88,100,.35); }\n .btn.danger:hover { background: rgba(255,88,100,.1); }\n .btn.success { color: var(--success); border-color: rgba(16,230,140,.35); }\n .btn.success:hover { background: rgba(16,230,140,.1); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Forms */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .field { margin-bottom: 12px; }\n .field label {\n display: block; margin-bottom: 5px;\n font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;\n color: var(--text-3);\n }\n .field input, .field textarea, .field select {\n width: 100%;\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 8px;\n padding: 9px 12px;\n font-size: 12.5px;\n color: var(--text);\n transition: all var(--duration) var(--easing);\n }\n .field input:focus, .field textarea:focus, .field select:focus {\n outline: none;\n border-color: var(--brand-cyan);\n box-shadow: 0 0 0 3px rgba(24,255,255,0.12);\n }\n .field textarea { resize: vertical; min-height: 70px; font-family: var(--font-mono); font-size: 12px; }\n .field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }\n\n /* Toggle switch */\n .switch { position: relative; display: inline-block; width: 38px; height: 22px; }\n .switch input { opacity: 0; width: 0; height: 0; }\n .switch .slider {\n position: absolute; inset: 0; cursor: pointer;\n background: var(--bg-elev-3);\n border: 1px solid var(--border);\n transition: var(--duration); border-radius: 12px;\n }\n .switch .slider::before {\n content: \"\"; position: absolute;\n height: 16px; width: 16px; left: 2px; top: 2px;\n background: var(--text-2); transition: var(--duration); border-radius: 50%;\n }\n .switch input:checked + .slider { background: var(--brand-gradient); border-color: transparent; }\n .switch input:checked + .slider::before { transform: translateX(16px); background: #fff; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Tables */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .table { width: 100%; border-collapse: collapse; }\n .table th, .table td { padding: 11px 14px; text-align: left; font-size: 12px; }\n .table th {\n background: var(--bg-elev-2);\n color: var(--text-3);\n font-weight: 700; letter-spacing: 0.06em;\n text-transform: uppercase; font-size: 10px;\n border-bottom: 1px solid var(--border-strong);\n position: sticky; top: 0; z-index: 1;\n }\n .table tbody tr { border-bottom: 1px solid var(--border); transition: background var(--duration-fast), box-shadow var(--duration-fast); }\n .table tbody tr:last-child { border-bottom: none; }\n .table tbody tr:hover { background: var(--brand-gradient-soft); box-shadow: inset 3px 0 0 var(--brand-cyan); }\n .table code { color: var(--brand-cyan); font-size: 11px; }\n\n .empty {\n padding: 32px 16px; text-align: center;\n color: var(--text-3); font-size: 12px;\n }\n .empty svg { width: 40px; height: 40px; color: var(--text-dim); margin: 0 auto 10px; display: block; }\n\n /* Progress meters */\n .meter { height: 6px; background: var(--bg-elev-3); border-radius: 4px; overflow: hidden; position: relative; }\n .meter .fill {\n height: 100%; border-radius: 4px;\n background: var(--brand-gradient);\n transition: width var(--duration-slow) var(--easing);\n }\n .meter .fill.good { background: var(--success); }\n .meter .fill.warn { background: var(--warning); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Feature toggles grid */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .feature-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; }\n .feature-toggle {\n background: var(--bg-elev-1);\n border: 1px solid var(--border);\n border-radius: 10px;\n padding: 12px 14px;\n display: flex; align-items: center; gap: 12px;\n transition: all var(--duration) var(--easing);\n }\n .feature-toggle:hover { border-color: var(--border-strong); }\n .feature-toggle .icon {\n width: 32px; height: 32px;\n background: var(--brand-gradient-soft);\n border-radius: 8px;\n display: flex; align-items: center; justify-content: center;\n color: var(--brand-cyan); flex-shrink: 0;\n }\n .feature-toggle .icon svg { width: 16px; height: 16px; }\n .feature-toggle .meta { flex: 1; min-width: 0; }\n .feature-toggle .meta .name { font-size: 12.5px; font-weight: 600; }\n .feature-toggle .meta .desc { font-size: 10.5px; color: var(--text-3); margin-top: 2px; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Command palette */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .cmd-overlay {\n position: fixed; inset: 0;\n background: rgba(3, 5, 9, 0.72);\n backdrop-filter: blur(8px);\n z-index: 2000;\n display: none; align-items: flex-start; justify-content: center;\n padding-top: 12vh;\n animation: fadeIn var(--duration) var(--easing);\n }\n .cmd-overlay.open { display: flex; }\n @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n .cmd-palette {\n width: min(640px, 92vw);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(36px);\n border: 1px solid var(--border-strong);\n border-radius: 14px;\n overflow: hidden;\n box-shadow: var(--shadow-lg);\n animation: paletteIn var(--duration-slow) var(--easing-spring);\n }\n @keyframes paletteIn { from { opacity: 0; transform: translateY(-20px) scale(0.96); } to { opacity: 1; transform: none; } }\n\n .cmd-palette input {\n width: 100%;\n background: transparent; border: none; outline: none;\n padding: 16px 20px;\n font-size: 14px; color: var(--text);\n border-bottom: 1px solid var(--border);\n }\n .cmd-palette .results { max-height: 420px; overflow-y: auto; padding: 6px; }\n .cmd-result {\n display: flex; align-items: center; gap: 10px;\n padding: 9px 12px; border-radius: 7px;\n cursor: pointer; color: var(--text-2);\n transition: all var(--duration-fast);\n }\n .cmd-result:hover, .cmd-result.selected { background: var(--bg-elev-2); color: var(--text); }\n .cmd-result svg { width: 15px; height: 15px; color: var(--brand-cyan); flex-shrink: 0; }\n .cmd-result .meta { flex: 1; font-size: 12.5px; }\n .cmd-result .hint { font-size: 10px; color: var(--text-3); font-family: var(--font-mono); }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Toasts */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .toast-container {\n position: fixed; bottom: 52px; right: 20px;\n display: flex; flex-direction: column; gap: 8px;\n z-index: 1500; pointer-events: none;\n }\n .toast {\n min-width: 260px; max-width: 360px;\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px);\n border: 1px solid var(--border-strong);\n border-left: 3px solid var(--brand-cyan);\n border-radius: 10px;\n padding: 10px 14px;\n pointer-events: auto;\n display: flex; align-items: flex-start; gap: 10px;\n animation: toastIn var(--duration-slow) var(--easing-spring);\n box-shadow: var(--shadow-lg);\n }\n .toast.success { border-left-color: var(--success); }\n .toast.warn { border-left-color: var(--warning); }\n .toast.error { border-left-color: var(--danger); }\n .toast.removing { animation: toastOut var(--duration-slow) var(--easing) forwards; }\n @keyframes toastIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: none; } }\n @keyframes toastOut { to { opacity: 0; transform: translateX(20px); } }\n .toast svg { width: 15px; height: 15px; color: var(--brand-cyan); flex-shrink: 0; margin-top: 1px; }\n .toast.success svg { color: var(--success); }\n .toast.warn svg { color: var(--warning); }\n .toast.error svg { color: var(--danger); }\n .toast .title { font-size: 12.5px; font-weight: 600; margin-bottom: 2px; }\n .toast .body { font-size: 11.5px; color: var(--text-2); line-height: 1.4; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Ticker + log panel */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .log-ticker {\n position: fixed; bottom: 0; left: 0; right: 0;\n height: var(--ticker-h);\n background: var(--bg-glass-strong);\n backdrop-filter: blur(24px);\n border-top: 1px solid var(--border);\n display: flex; align-items: center; gap: 12px;\n padding: 0 20px;\n font-family: var(--font-mono); font-size: 10.5px;\n color: var(--text-3);\n z-index: 600;\n }\n .log-ticker .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); animation: pulse 1.5s infinite; flex-shrink: 0; }\n .log-ticker .scroll { flex: 1; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }\n .log-panel {\n position: fixed; bottom: var(--ticker-h); left: 0; right: 0;\n height: 300px;\n background: var(--bg-elev-1);\n border-top: 1px solid var(--border);\n padding: 16px 20px;\n overflow-y: auto;\n font-family: var(--font-mono); font-size: 11.5px;\n color: var(--text-2);\n z-index: 599;\n transform: translateY(100%);\n transition: transform var(--duration-slow) var(--easing);\n box-shadow: 0 -8px 32px rgba(0,0,0,0.3);\n }\n .log-panel.open { transform: translateY(0); }\n .log-panel .head {\n display: flex; justify-content: space-between; align-items: center;\n margin-bottom: 10px;\n }\n .log-panel .head .title { color: var(--brand-cyan); font-weight: 700; letter-spacing: 0.04em; }\n .log-row {\n padding: 4px 0;\n border-bottom: 1px solid var(--border);\n display: grid; grid-template-columns: 90px 130px 1fr; gap: 10px; align-items: baseline;\n }\n .log-row .t { color: var(--text-dim); font-size: 10px; }\n .log-row .src { color: var(--brand-purple); font-weight: 600; font-size: 10.5px; }\n .log-row .type { color: var(--brand-cyan); font-weight: 600; font-size: 10.5px; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Responsive */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .menu-toggle { display: none; }\n\n @media (max-width: 1024px) {\n .grid.cols-3, .grid.cols-4 { grid-template-columns: repeat(2, 1fr); }\n .card.span-2 { grid-column: 1 / -1; }\n }\n\n @media (max-width: 768px) {\n header { padding: 0 14px; gap: 10px; }\n .header-stats { display: none; }\n .brand .state { display: none; }\n .menu-toggle { display: flex; }\n aside.sidebar {\n position: fixed;\n top: var(--header-h); bottom: 0;\n width: 280px; z-index: 90;\n transform: translateX(-100%);\n transition: transform var(--duration-slow) var(--easing);\n }\n aside.sidebar.open { transform: translateX(0); box-shadow: var(--shadow-lg); }\n .layout { grid-template-columns: 1fr; }\n main { padding: 14px; padding-bottom: 60px; }\n .grid, .grid.cols-2, .grid.cols-3, .grid.cols-4 { grid-template-columns: 1fr; }\n .card.span-2 { grid-column: auto; }\n .section-title { font-size: 18px; }\n .field-row { grid-template-columns: 1fr; }\n }\n\n /* Chart canvas wrapper */\n .chart-wrap { position: relative; height: 220px; }\n .chart-wrap.lg { height: 320px; }\n\n /* Live Graph canvas */\n .graph-wrap {\n position: relative;\n background: radial-gradient(ellipse at center, rgba(168,85,247,0.04) 0%, transparent 70%);\n border-radius: 12px; overflow: hidden;\n border: 1px solid var(--border);\n }\n .graph-wrap canvas { display: block; width: 100%; height: 560px; }\n\n /* Helpers */\n .mono { font-family: var(--font-mono); font-size: 11.5px; }\n .tag-code { font-family: var(--font-mono); background: var(--bg-elev-2); padding: 1px 6px; border-radius: 4px; font-size: 11px; }\n .flex { display: flex; }\n .flex.center { align-items: center; }\n .flex.gap { gap: 8px; }\n .flex.between { justify-content: space-between; }\n .grow { flex: 1; }\n .hidden { display: none; }\n .mt { margin-top: 10px; } .mt-lg { margin-top: 20px; }\n .mb { margin-bottom: 10px; } .mb-lg { margin-bottom: 20px; }\n\n /* Accent bar */\n .accent-bar {\n height: 3px; border-radius: 2px;\n background: var(--brand-gradient);\n margin-bottom: 18px;\n box-shadow: 0 0 12px rgba(24,255,255,0.35);\n }\n\n /* Skeleton loader */\n .skel { background: linear-gradient(90deg, var(--bg-elev-2) 0%, var(--bg-elev-3) 50%, var(--bg-elev-2) 100%); background-size: 200% 100%; animation: skel 1.2s linear infinite; border-radius: 6px; }\n @keyframes skel { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Realtime activity (v6.1) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .live-badge {\n display: inline-flex; align-items: center; gap: 5px;\n font-family: var(--font-mono); font-size: 9px; font-weight: 800; letter-spacing: 0.12em;\n color: var(--success); background: rgba(16,230,140,.12);\n border: 1px solid rgba(16,230,140,.32); border-radius: 999px;\n padding: 2px 8px; vertical-align: middle;\n }\n .live-badge::before {\n content: \"\"; width: 6px; height: 6px; border-radius: 50%;\n background: var(--success); box-shadow: 0 0 6px var(--success);\n animation: pulse 1.3s infinite;\n }\n .live-badge.paused { color: var(--warning); background: rgba(255,181,63,.12); border-color: rgba(255,181,63,.32); }\n .live-badge.paused::before { background: var(--warning); box-shadow: none; animation: none; }\n .filter-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 14px; }\n .chip {\n padding: 5px 12px; border-radius: 999px; font-size: 11.5px; font-weight: 600;\n background: var(--bg-elev-2); border: 1px solid var(--border); color: var(--text-2);\n transition: all var(--duration-fast) var(--easing);\n }\n .chip:hover { border-color: var(--border-strong); color: var(--text); }\n .chip.active { background: var(--brand-gradient); color: #fff; border-color: transparent; }\n .act-search {\n background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 8px;\n padding: 7px 12px; color: var(--text); font-size: 12px; min-width: 180px;\n }\n .act-search:focus { outline: none; border-color: var(--brand-cyan); box-shadow: 0 0 0 3px rgba(24,255,255,0.12); }\n tr.act-fresh { animation: actFreshIn var(--duration-slow) var(--easing); }\n @keyframes actFreshIn { from { background: rgba(24,255,255,0.12); opacity: 0; transform: translateY(-4px); } to { background: transparent; opacity: 1; transform: none; } }\n .conn-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--success); box-shadow: 0 0 6px var(--success); transition: all var(--duration); }\n .conn-dot.offline { background: var(--warning); box-shadow: none; animation: pulse 1s infinite; }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Memory Constellation (v6.2) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .const-wrap {\n position: relative;\n height: 600px;\n background:\n radial-gradient(ellipse at 30% 20%, rgba(24,255,255,0.05) 0%, transparent 55%),\n radial-gradient(ellipse at 75% 80%, rgba(168,85,247,0.06) 0%, transparent 55%);\n overflow: hidden;\n cursor: grab;\n }\n .const-wrap.dragging { cursor: grabbing; }\n .const-wrap canvas { display: block; width: 100%; height: 100%; }\n .const-legend {\n position: absolute; top: 12px; left: 12px;\n display: flex; flex-direction: column; gap: 5px;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border); border-radius: 8px; padding: 8px 11px;\n font-size: 11px; color: var(--text-2); pointer-events: none;\n }\n .const-legend .lg-row { display: flex; align-items: center; gap: 7px; }\n .const-legend .lg-dot { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 6px currentColor; }\n .const-meta {\n position: absolute; top: 12px; right: 12px;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border); border-radius: 8px; padding: 6px 11px;\n font-family: var(--font-mono); font-size: 10.5px; color: var(--brand-cyan); pointer-events: none;\n }\n .const-hint {\n position: absolute; bottom: 10px; left: 12px;\n font-size: 10.5px; color: var(--text-3); pointer-events: none;\n background: var(--bg-glass); border-radius: 6px; padding: 3px 9px;\n }\n .const-empty {\n position: absolute; inset: 0; display: none;\n flex-direction: column; align-items: center; justify-content: center;\n text-align: center; gap: 8px; padding: 24px;\n }\n .const-empty.show { display: flex; }\n .const-empty svg { width: 52px; height: 52px; color: var(--text-dim); margin-bottom: 4px; }\n .const-empty .ce-title { font-size: 15px; font-weight: 700; color: var(--text-2); }\n .const-empty .ce-msg { font-size: 12px; color: var(--text-3); max-width: 360px; line-height: 1.5; }\n .const-drawer {\n position: absolute; top: 0; right: 0; bottom: 0; width: min(340px, 80%);\n background: var(--bg-glass-strong); backdrop-filter: blur(28px);\n border-left: 1px solid var(--border-strong);\n transform: translateX(100%); transition: transform var(--duration-slow) var(--easing);\n padding: 16px; overflow-y: auto; z-index: 5;\n }\n .const-drawer.open { transform: translateX(0); box-shadow: -12px 0 40px rgba(0,0,0,0.35); }\n .const-drawer-close {\n position: absolute; top: 10px; right: 12px;\n width: 26px; height: 26px; border-radius: 6px; font-size: 18px; line-height: 1;\n color: var(--text-2); background: var(--bg-elev-2); border: 1px solid var(--border);\n }\n .const-drawer-close:hover { color: var(--text); background: var(--bg-elev-3); }\n .cd-type { font-size: 10px; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }\n .cd-title { font-size: 15px; font-weight: 700; margin: 6px 0 12px; line-height: 1.35; word-break: break-word; }\n .cd-section { margin-top: 12px; }\n .cd-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-3); margin-bottom: 4px; font-weight: 700; }\n .cd-content { font-size: 12px; color: var(--text-2); line-height: 1.5; word-break: break-word; }\n .const-tip {\n position: absolute; pointer-events: none; z-index: 6; display: none;\n background: var(--bg-glass-strong); backdrop-filter: blur(12px);\n border: 1px solid var(--border-strong); border-radius: 7px; padding: 6px 10px;\n font-size: 11px; color: var(--text); max-width: 260px;\n transform: translate(-50%, calc(-100% - 12px));\n }\n .const-tip.show { display: block; }\n .tag-cloud { display: flex; flex-wrap: wrap; gap: 7px; }\n .tag-cloud .tc {\n border: 1px solid var(--border); border-radius: 999px; padding: 4px 11px;\n font-size: 12px; color: var(--text-2); background: var(--bg-elev-1);\n }\n .tag-cloud .tc b { color: var(--brand-cyan); font-variant-numeric: tabular-nums; }\n\n /* Overview constellation hero */\n .const-hero { background: linear-gradient(120deg, rgba(24,255,255,0.08), rgba(168,85,247,0.08) 60%, transparent); }\n .const-hero:hover { border-color: var(--border-accent); box-shadow: var(--shadow-glow); }\n .const-hero-inner { display: flex; align-items: center; gap: 20px; }\n .const-hero-art { flex-shrink: 0; width: 132px; height: 96px; }\n .const-hero-art svg { width: 100%; height: 100%; filter: drop-shadow(0 0 8px rgba(24,255,255,0.35)); }\n .const-hero-art .he { stroke: rgba(24,255,255,0.4); stroke-width: 1.2; }\n .const-hero-art circle { animation: synapse 1.6s ease-in-out infinite; transform-box: fill-box; transform-origin: center; }\n .const-hero-body { flex: 1; min-width: 0; }\n .const-hero-title { font-size: 19px; font-weight: 800; letter-spacing: -0.4px; background: var(--brand-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }\n .const-hero-sub { font-size: 12.5px; color: var(--text-3); margin: 4px 0 10px; }\n .const-hero-stats { display: flex; gap: 18px; font-size: 12px; color: var(--text-2); }\n .const-hero-stats b { font-size: 17px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; margin-right: 4px; }\n @media (max-width: 768px) { .const-hero-art { display: none; } .const-hero-inner { gap: 12px; } }\n\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Revived tab visualizations (v6.2) */\n /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n /* Reusable empty-state */\n .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 8px; padding: 36px 20px; }\n .empty-state svg { width: 44px; height: 44px; color: var(--text-dim); margin-bottom: 4px; }\n .empty-state .es-title { font-size: 14px; font-weight: 700; color: var(--text-2); }\n .empty-state .es-msg { font-size: 12px; color: var(--text-3); max-width: 380px; line-height: 1.5; }\n .empty-state .es-cta { margin-top: 6px; }\n\n /* Risk gauge + matrix (Pre-Mortem) */\n .risk-head { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; }\n .risk-gauge { position: relative; width: 96px; height: 96px; flex-shrink: 0; }\n .risk-gauge canvas { width: 96px; height: 96px; }\n .risk-gauge .rg-num { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 900; }\n .risk-matrix { display: grid; grid-template-columns: 64px repeat(4, 1fr); gap: 5px; margin: 14px 0; }\n .risk-matrix .rm-h { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-3); display: flex; align-items: center; justify-content: center; padding: 4px; }\n .risk-matrix .rm-yl { font-size: 10px; color: var(--text-3); display: flex; align-items: center; justify-content: flex-end; padding-right: 6px; }\n .risk-matrix .rm-cell { aspect-ratio: 2.6/1; border-radius: 6px; border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13px; color: #fff; min-height: 30px; transition: all var(--duration); }\n .risk-matrix .rm-cell.empty { background: var(--bg-elev-1); color: var(--text-dim); font-weight: 400; }\n .fail-card { padding: 12px 14px; background: var(--bg-elev-1); border: 1px solid var(--border); border-left-width: 3px; border-radius: 8px; margin-bottom: 8px; }\n .fail-card .fc-head { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 6px; }\n .fail-card .fc-desc { font-size: 12.5px; color: var(--text); line-height: 1.45; }\n .fail-card .fc-mit { font-size: 11.5px; color: var(--text-2); margin-top: 6px; padding-left: 14px; border-left: 2px solid var(--success); }\n\n /* Attention heat bars */\n .heat-row { margin-bottom: 10px; }\n .heat-row .hr-top { display: flex; justify-content: space-between; align-items: center; font-size: 11.5px; margin-bottom: 4px; gap: 8px; }\n .heat-row .hr-pct { font-family: var(--font-mono); font-weight: 800; flex-shrink: 0; }\n .heat-row .hr-track { height: 22px; background: var(--bg-elev-1); border-radius: 5px; overflow: hidden; border: 1px solid var(--border); }\n .heat-row .hr-fill { height: 100%; border-radius: 5px 0 0 5px; transition: width var(--duration-slow) var(--easing); }\n .heat-row .hr-reason { font-size: 10.5px; color: var(--text-3); margin-top: 3px; }\n\n /* Reputation trust cards */\n .trust-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; }\n .trust-card { background: var(--bg-elev-1); border: 1px solid var(--border); border-radius: 12px; padding: 16px; display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center; transition: all var(--duration); }\n .trust-card:hover { border-color: var(--border-accent); transform: translateY(-2px); }\n .trust-ring { position: relative; width: 92px; height: 92px; }\n .trust-ring canvas { width: 92px; height: 92px; }\n .trust-ring .tr-pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 19px; font-weight: 900; }\n .trust-card .tc-name { font-weight: 700; font-size: 13px; }\n .trust-card .tc-sub { font-size: 11px; color: var(--text-3); }\n\n /* Causal / DAG canvas */\n .dag-wrap { position: relative; height: 420px; background: radial-gradient(ellipse at center, rgba(168,85,247,0.04), transparent 70%); border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }\n .dag-wrap canvas { display: block; width: 100%; height: 100%; }\n .dag-empty { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--text-3); font-size: 12.5px; text-align: center; padding: 20px; }\n</style>\n</head>\n<body>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Header\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<header>\n <button class=\"icon-btn menu-toggle\" onclick=\"toggleSidebar()\" aria-label=\"Menu\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"3\" y1=\"6\" x2=\"21\" y2=\"6\"/><line x1=\"3\" y1=\"12\" x2=\"21\" y2=\"12\"/><line x1=\"3\" y1=\"18\" x2=\"21\" y2=\"18\"/></svg>\n </button>\n\n <div class=\"brain-hero\" title=\"Hive Mind \u2014 learning \u00B7 reflecting \u00B7 charging\">\n <div class=\"aura\"></div>\n <svg viewBox=\"0 0 100 100\">\n <circle class=\"hemi charge\" cx=\"50\" cy=\"50\" r=\"46\"/>\n <circle class=\"hemi charge\" cx=\"50\" cy=\"50\" r=\"40\" style=\"animation-duration:3s;animation-direction:reverse\"/>\n <path class=\"hemi\" d=\"M 48 22 C 30 22, 18 32, 18 48 C 18 60, 26 72, 38 78 C 42 82, 46 80, 48 76 L 48 22\"/>\n <path class=\"hemi hemi-r\" d=\"M 52 22 C 70 22, 82 32, 82 48 C 82 60, 74 72, 62 78 C 58 82, 54 80, 52 76 L 52 22\"/>\n <path class=\"hemi\" d=\"M 30 40 Q 38 38, 44 44\"/>\n <path class=\"hemi\" d=\"M 28 55 Q 36 55, 44 58\"/>\n <path class=\"hemi\" d=\"M 32 68 Q 40 66, 46 72\"/>\n <path class=\"hemi hemi-r\" d=\"M 70 40 Q 62 38, 56 44\"/>\n <path class=\"hemi hemi-r\" d=\"M 72 55 Q 64 55, 56 58\"/>\n <path class=\"hemi hemi-r\" d=\"M 68 68 Q 60 66, 54 72\"/>\n <circle class=\"synapse\" cx=\"30\" cy=\"42\" r=\"1.8\" fill=\"#18ffff\"/>\n <circle class=\"synapse\" cx=\"70\" cy=\"42\" r=\"1.8\" fill=\"#a855f7\" style=\"animation-delay:0.3s\"/>\n <circle class=\"synapse\" cx=\"35\" cy=\"62\" r=\"1.6\" fill=\"#10e68c\" style=\"animation-delay:0.6s\"/>\n <circle class=\"synapse\" cx=\"65\" cy=\"62\" r=\"1.6\" fill=\"#ffb53f\" style=\"animation-delay:0.9s\"/>\n <circle class=\"synapse\" cx=\"50\" cy=\"35\" r=\"1.4\" fill=\"#ec4899\" style=\"animation-delay:0.15s\"/>\n <circle class=\"synapse\" cx=\"50\" cy=\"70\" r=\"1.4\" fill=\"#18ffff\" style=\"animation-delay:0.75s\"/>\n </svg>\n </div>\n\n <div class=\"brand\">\n <div class=\"name\">Shadow Brain <span style=\"color:var(--text-3);font-size:10px;font-weight:600\">v7.2 Hive Mind</span></div>\n <div class=\"state\" id=\"brain-state\">learning \u00B7 reflecting \u00B7 charging</div>\n </div>\n\n <div class=\"header-stats\" id=\"header-stats\">\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-agents\">\u2014</span> agents</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-memories\">\u2014</span> memories</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-dreams\">\u2014</span> reflections</div>\n <div class=\"stat-pill\"><span class=\"n\" id=\"stat-tools\">\u2014</span> tools</div>\n </div>\n\n <div class=\"header-right\">\n <span class=\"conn-dot\" id=\"conn-dot\" title=\"Realtime connection: connecting\"></span>\n <button class=\"kbd-hint\" onclick=\"openCmdPalette()\" title=\"Open command palette\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"11\" cy=\"11\" r=\"8\"/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"/></svg>\n Search <kbd>\u2318K</kbd>\n </button>\n <button class=\"icon-btn\" onclick=\"toggleTheme()\" title=\"Toggle theme\">\n <svg id=\"theme-icon\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"5\"/><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"/><line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"/><line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"/><line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"/><line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"/><line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"/><line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"/><line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"/></svg>\n </button>\n <button class=\"icon-btn\" onclick=\"toggleLogPanel()\" title=\"Activity log\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"21\" y1=\"10\" x2=\"3\" y2=\"10\"/><line x1=\"21\" y1=\"6\" x2=\"3\" y2=\"6\"/><line x1=\"21\" y1=\"14\" x2=\"3\" y2=\"14\"/><line x1=\"21\" y1=\"18\" x2=\"3\" y2=\"18\"/></svg></button>\n <button class=\"btn danger sm\" onclick=\"stopBrain()\" title=\"Stop Shadow Brain (npm process)\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"6\" y=\"6\" width=\"12\" height=\"12\" rx=\"1\"/></svg>Stop</button>\n <button class=\"btn primary sm\" onclick=\"refresh()\" title=\"Refresh all\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"23 4 23 10 17 10\"/><polyline points=\"1 20 1 14 7 14\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>Refresh</button>\n </div>\n</header>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Layout: sidebar + main\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div class=\"layout\">\n <aside class=\"sidebar\" id=\"sidebar\">\n <div class=\"mode-toggle\" id=\"mode-toggle\" title=\"Simple shows the essentials. Advanced reveals every feature.\">\n <button class=\"mode-btn active\" data-mode=\"simple\" onclick=\"setMode('simple')\">Simple</button>\n <button class=\"mode-btn\" data-mode=\"advanced\" onclick=\"setMode('advanced')\">Advanced</button>\n </div>\n\n <div class=\"nav-group\">\n <div class=\"nav-group-label\">Main</div>\n <button class=\"nav-item active\" data-tab=\"overview\" data-title=\"Home\" title=\"Your starting point \u2014 a quick summary of everything the brain knows and is doing.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/><polyline points=\"9 22 9 12 15 12 15 22\"/></svg>\n Home\n </button>\n <button class=\"nav-item\" data-tab=\"intelligence\" data-title=\"Brain Map\" title=\"A visual map of everything the brain remembers \u2014 memories, projects, and the tools that created them.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/><line x1=\"7\" y1=\"7\" x2=\"10\" y2=\"10.5\"/><line x1=\"17\" y1=\"7\" x2=\"14\" y2=\"10.5\"/><line x1=\"7\" y1=\"17\" x2=\"10\" y2=\"13.5\"/><line x1=\"17\" y1=\"17\" x2=\"14\" y2=\"13.5\"/></svg>\n Brain Map<span class=\"badge-count\" id=\"badge-intelligence\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"memory\" data-title=\"Memories\" title=\"Browse and search everything the brain has learned and saved.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M21 12c0 1.66-4 3-9 3s-9-1.34-9-3\"/><path d=\"M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5\"/></svg>\n Memories\n </button>\n <button class=\"nav-item\" data-tab=\"chat\" data-title=\"Ask the Brain\" title=\"Ask a question in plain language and get answers from the brain's memory.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n Ask the Brain\n </button>\n <button class=\"nav-item\" data-tab=\"activity\" data-title=\"Activity\" title=\"A live feed of what the brain and your tools are doing right now.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/></svg>\n Activity\n </button>\n <button class=\"nav-item\" data-tab=\"agents\" data-title=\"Connected Tools\" title=\"See which AI coding tools (Claude, Cursor, Codex\u2026) are connected to your brain.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n Connected Tools<span class=\"badge-count\" id=\"badge-agents\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"livebrain\" data-title=\"Brain Health\" title=\"A health check of your brain's memory quality, with tips to improve it.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M22 12h-4l-3 9L9 3l-3 9H2\"/></svg>\n Brain Health\n </button>\n <button class=\"nav-item\" data-tab=\"config\" data-title=\"Settings\" title=\"Set up and adjust how Shadow Brain works.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><path d=\"M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z\"/></svg>\n Settings\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Explore</div>\n <button class=\"nav-item\" data-tab=\"ww2br\" data-title=\"Project Memory\" title=\"Memory and status for one specific project.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 19V5\"/><path d=\"M4 6h9l1 2h6v8h-9l-1-2H4\"/></svg>\n Project Memory<span class=\"badge-count\" id=\"badge-ww2br\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"graph\" data-title=\"Live Network\" title=\"A live animation of your tools talking to the brain.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"12\" cy=\"12\" r=\"8\"/></svg>\n Live Network\n </button>\n <button class=\"nav-item\" data-tab=\"seeder\" data-title=\"Add Knowledge\" title=\"Research a topic and save it as verified, source-backed knowledge.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 2v20\"/><path d=\"M5 9c0-3 2-5 7-7 5 2 7 4 7 7 0 5-4 8-7 8s-7-3-7-8z\"/><path d=\"M8 12h8\"/></svg>\n Add Knowledge<span class=\"badge-count\" id=\"badge-seeder\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"garden\" data-title=\"Memory Garden\" title=\"A relaxing visual of your memories growing over time.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s-8-4-8-12c0-4 3-7 8-7s8 3 8 7c0 8-8 12-8 12z\"/><path d=\"M12 17v-5M12 7V5\"/></svg>\n Memory Garden\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">How the Brain Thinks</div>\n <button class=\"nav-item\" data-tab=\"sabb\" data-title=\"Sub-Agents\" title=\"When a tool spawns helper agents, the brain shares the right context with them.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"18\" cy=\"5\" r=\"3\"/><circle cx=\"6\" cy=\"12\" r=\"3\"/><circle cx=\"18\" cy=\"19\" r=\"3\"/><line x1=\"8.59\" y1=\"13.51\" x2=\"15.42\" y2=\"17.49\"/><line x1=\"15.41\" y1=\"6.51\" x2=\"8.59\" y2=\"10.49\"/></svg>\n Sub-Agents<span class=\"badge-count\" id=\"badge-sabb\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"causal\" data-title=\"Decision Trails\" title=\"Trace why a decision was made \u2014 what led to it and what it caused.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></svg>\n Decision Trails<span class=\"badge-count\" id=\"badge-causal\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"dream\" data-title=\"Reflections\" title=\"While idle, the brain reviews past work and finds patterns and lessons.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>\n Reflections<span class=\"badge-count\" id=\"badge-dream\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"debate\" data-title=\"Debates\" title=\"Have multiple AI viewpoints argue a tough decision, then get a verdict.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>\n Debates\n </button>\n <button class=\"nav-item\" data-tab=\"premortem\" data-title=\"Risk Check\" title=\"Before a big task, see what went wrong last time so you can avoid it.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/></svg>\n Risk Check\n </button>\n <button class=\"nav-item\" data-tab=\"branch\" data-title=\"Branch Memory\" title=\"Memory that follows your git branch, so each branch has its own context.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"6\" y1=\"3\" x2=\"6\" y2=\"15\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/></svg>\n Branch Memory\n </button>\n <button class=\"nav-item\" data-tab=\"forget\" data-title=\"Memory Cleanup\" title=\"Old, unused memories fade while important ones are kept \u2014 like human memory.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>\n Memory Cleanup\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Safety & Trust</div>\n <button class=\"nav-item\" data-tab=\"collision\" data-title=\"Conflicts\" title=\"Catches two tools about to edit the same code at the same time.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"4.93\" y1=\"4.93\" x2=\"19.07\" y2=\"19.07\"/></svg>\n Conflicts<span class=\"badge-count\" id=\"badge-collision\">0</span>\n </button>\n <button class=\"nav-item\" data-tab=\"reputation\" data-title=\"Trust Scores\" title=\"How reliable each AI tool has been, based on its track record.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"8\" r=\"7\"/><polyline points=\"8.21 13.89 7 23 12 20 17 23 15.79 13.88\"/></svg>\n Trust Scores\n </button>\n <button class=\"nav-item\" data-tab=\"calibration\" data-title=\"Accuracy\" title=\"How well each tool's confidence matches how often it's actually right.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"/><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"/><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"/></svg>\n Accuracy\n </button>\n <button class=\"nav-item\" data-tab=\"attention\" data-title=\"Focus Map\" title=\"Shows which memories most influenced a particular decision.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"4\"/><line x1=\"21.17\" y1=\"8\" x2=\"12\" y2=\"8\"/></svg>\n Focus Map\n </button>\n <button class=\"nav-item\" data-tab=\"formal\" data-title=\"Code Rules\" title=\"Turn plain-language rules into checks your linters can enforce.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"9 11 12 14 22 4\"/><path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/></svg>\n Code Rules\n </button>\n <button class=\"nav-item\" data-tab=\"privacy\" data-title=\"Privacy & Safety\" title=\"Keep everything offline and block risky actions.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>\n Privacy & Safety\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">Cost & Sharing</div>\n <button class=\"nav-item\" data-tab=\"tokens\" data-title=\"Cost & Usage\" title=\"Track how much your AI tools are spending and where you can save.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"23\"/><path d=\"M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6\"/></svg>\n Cost & Usage\n </button>\n <button class=\"nav-item\" data-tab=\"pr\" data-title=\"Code Review\" title=\"Generate a review for a pull request using what the brain knows.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"18\" cy=\"18\" r=\"3\"/><circle cx=\"6\" cy=\"6\" r=\"3\"/><path d=\"M13 6h3a2 2 0 0 1 2 2v7\"/><line x1=\"6\" y1=\"9\" x2=\"6\" y2=\"21\"/></svg>\n Code Review\n </button>\n <button class=\"nav-item\" data-tab=\"voice\" data-title=\"Voice\" title=\"Talk to the brain and hear answers, hands-free.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"/><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"/><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"/></svg>\n Voice\n </button>\n <button class=\"nav-item\" data-tab=\"team\" data-title=\"Team Sync\" title=\"Share brain memory with your teammates.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"/></svg>\n Team Sync\n </button>\n <button class=\"nav-item\" data-tab=\"exchange\" data-title=\"Share Brain\" title=\"Export a memory pack to share, or import one from someone else.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"16 3 21 3 21 8\"/><line x1=\"4\" y1=\"20\" x2=\"21\" y2=\"3\"/><polyline points=\"21 16 21 21 16 21\"/><line x1=\"15\" y1=\"15\" x2=\"21\" y2=\"21\"/><line x1=\"4\" y1=\"4\" x2=\"9\" y2=\"9\"/></svg>\n Share Brain\n </button>\n </div>\n\n <div class=\"nav-group\" data-adv=\"1\">\n <div class=\"nav-group-label\">System</div>\n <button class=\"nav-item\" data-tab=\"features\" data-title=\"Features\" title=\"Turn individual brain features on or off.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"1\" y=\"5\" width=\"22\" height=\"14\" rx=\"7\"/><circle cx=\"16\" cy=\"12\" r=\"3\"/></svg>\n Features\n </button>\n <button class=\"nav-item\" data-tab=\"models\" data-title=\"AI Models\" title=\"Choose and configure which AI models the brain uses.\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z\"/></svg>\n AI Models\n </button>\n </div>\n </aside>\n\n <main id=\"main\">\n\n <!-- \u2550\u2550\u2550\u2550 OVERVIEW \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section active\" id=\"tab-overview\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z\"/><polyline points=\"9 22 9 12 15 12 15 22\"/></svg>Welcome to your Shadow Brain</h1>\n <div class=\"section-subtitle\">A shared memory for your AI coding tools \u2014 private, on your machine. Here's what it knows and is doing right now.</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"runDream()\" title=\"Have the brain review recent work and find patterns\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"/></svg>Reflect Now</button>\n <button class=\"btn\" onclick=\"runForget()\" title=\"Tidy memory \u2014 fade old entries, keep the important ones\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"1 4 1 10 7 10\"/><path d=\"M3.51 15a9 9 0 1 0 2.13-9.36L1 10\"/></svg>Clean Up</button>\n <button class=\"btn danger\" onclick=\"toggleAirGap()\" title=\"Block all outgoing network \u2014 fully offline\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/></svg>Go Offline</button>\n </div>\n </div>\n\n <div class=\"grid cols-4\">\n <div class=\"card stat-card accent\">\n <div class=\"label\">Tools Connected</div>\n <div class=\"value accent\" id=\"ov-agents\">\u2014</div>\n <div class=\"trend\">AI tools linked to your brain</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Memories Stored</div>\n <div class=\"value\" id=\"ov-memories\">\u2014</div>\n <div class=\"trend\" id=\"ov-memories-sub\">across all projects</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Helper Agents</div>\n <div class=\"value\" id=\"ov-subagents\">\u2014</div>\n <div class=\"trend\" id=\"ov-subagents-sub\">graduated / quarantined</div>\n </div>\n <div class=\"card stat-card accent\">\n <div class=\"label\">Active Tool</div>\n <div class=\"value\" style=\"font-size:22px\" id=\"ov-current-agent\">\u2014</div>\n <div class=\"trend\" id=\"ov-current-agent-sub\">using the brain right now</div>\n </div>\n </div>\n\n <div class=\"card accent span-full mt-lg const-hero\" onclick=\"switchTab('intelligence','')\" style=\"cursor:pointer\">\n <div class=\"const-hero-inner\">\n <div class=\"const-hero-art\">\n <svg viewBox=\"0 0 120 90\" xmlns=\"http://www.w3.org/2000/svg\">\n <line x1=\"60\" y1=\"45\" x2=\"24\" y2=\"20\" class=\"he\"/><line x1=\"60\" y1=\"45\" x2=\"98\" y2=\"22\" class=\"he\"/>\n <line x1=\"60\" y1=\"45\" x2=\"22\" y2=\"68\" class=\"he\"/><line x1=\"60\" y1=\"45\" x2=\"100\" y2=\"66\" class=\"he\"/>\n <line x1=\"60\" y1=\"45\" x2=\"60\" y2=\"14\" class=\"he\"/><line x1=\"24\" y1=\"20\" x2=\"22\" y2=\"68\" class=\"he\"/>\n <circle cx=\"60\" cy=\"45\" r=\"9\" fill=\"#18ffff\"/>\n <circle cx=\"24\" cy=\"20\" r=\"5\" fill=\"#10e68c\"/><circle cx=\"98\" cy=\"22\" r=\"5\" fill=\"#ffb53f\"/>\n <circle cx=\"22\" cy=\"68\" r=\"5\" fill=\"#a855f7\"/><circle cx=\"100\" cy=\"66\" r=\"4\" fill=\"#18ffff\"/>\n <circle cx=\"60\" cy=\"14\" r=\"4\" fill=\"#ec4899\"/>\n </svg>\n </div>\n <div class=\"const-hero-body\">\n <div class=\"const-hero-title\">Memory Constellation</div>\n <div class=\"const-hero-sub\">Explore every memory, project, agent & tag as a living, importance-ranked graph.</div>\n <div class=\"const-hero-stats\">\n <span><b id=\"hero-const-nodes\">\u2014</b> nodes</span>\n <span><b id=\"hero-const-edges\">\u2014</b> links</span>\n <span><b id=\"hero-const-mem\">\u2014</b> memories</span>\n </div>\n </div>\n <button class=\"btn primary lg\" onclick=\"event.stopPropagation();switchTab('intelligence','')\">Explore <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/></svg></button>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><line x1=\"12\" y1=\"20\" x2=\"12\" y2=\"10\"/><line x1=\"18\" y1=\"20\" x2=\"18\" y2=\"4\"/><line x1=\"6\" y1=\"20\" x2=\"6\" y2=\"16\"/></svg>Reported Token Usage by Tool</div></div>\n <div class=\"chart-wrap\"><canvas id=\"chart-tokens\"></canvas></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg>Recent Activity</div></div>\n <div id=\"ov-timeline\" style=\"max-height:220px;overflow-y:auto;font-size:11.5px\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card span-full accent\">\n <div class=\"card-header\">\n <div class=\"card-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"22 12 18 12 15 21 9 3 6 12 2 12\"/></svg>Live Pulse <span class=\"live-badge\" id=\"ov-pulse-badge\">LIVE</span></div>\n <span class=\"pill cyan\" id=\"ov-pulse-rate\">0 / min</span>\n </div>\n <div id=\"ov-pulse\" class=\"brain-feed\" style=\"max-height:300px;overflow-y:auto\"><div class=\"empty-tight\">Awaiting live activity\u2026</div></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Module Status</div></div>\n <div id=\"ov-modules\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">System Health</div></div>\n <div id=\"ov-health\"></div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 INTELLIGENCE (Memory Constellation + Analytics) \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-intelligence\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/><line x1=\"7\" y1=\"7\" x2=\"10\" y2=\"10.5\"/><line x1=\"17\" y1=\"7\" x2=\"14\" y2=\"10.5\"/><line x1=\"7\" y1=\"17\" x2=\"10\" y2=\"13.5\"/><line x1=\"17\" y1=\"17\" x2=\"14\" y2=\"13.5\"/></svg>Intelligence</h1>\n <div class=\"section-subtitle\">Memory Constellation \u2014 every memory, project, agent & tag as a living graph, sized by importance</div>\n </div>\n <div class=\"section-actions\">\n <input id=\"const-search\" class=\"act-search\" placeholder=\"Search nodes\u2026\" oninput=\"highlightConstSearch()\">\n <button class=\"btn\" onclick=\"rebuildConstellation()\" title=\"Rebuild from the latest brain state\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"23 4 23 10 17 10\"/><path d=\"M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15\"/></svg>Rebuild</button>\n <button class=\"btn\" onclick=\"resetConstellationView()\" title=\"Reset zoom & pan\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"16\"/><line x1=\"8\" y1=\"12\" x2=\"16\" y2=\"12\"/></svg>Reset</button>\n </div>\n </div>\n\n <div class=\"filter-chips\" id=\"const-chips\">\n <button class=\"chip active\" data-ntype=\"\" onclick=\"setConstFilter(this,'')\">All</button>\n <button class=\"chip\" data-ntype=\"memory\" onclick=\"setConstFilter(this,'memory')\">Memories</button>\n <button class=\"chip\" data-ntype=\"project\" onclick=\"setConstFilter(this,'project')\">Projects</button>\n <button class=\"chip\" data-ntype=\"agent\" onclick=\"setConstFilter(this,'agent')\">Agents</button>\n <button class=\"chip\" data-ntype=\"tag\" onclick=\"setConstFilter(this,'tag')\">Tags</button>\n </div>\n\n <div class=\"card\" style=\"padding:0;overflow:hidden\">\n <div class=\"const-wrap\" id=\"const-wrap\">\n <canvas id=\"const-canvas\"></canvas>\n <div class=\"const-legend\">\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#18ffff\"></span>Memory</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#10e68c\"></span>Project</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#ffb53f\"></span>Agent</div>\n <div class=\"lg-row\"><span class=\"lg-dot\" style=\"background:#a855f7\"></span>Tag</div>\n </div>\n <div class=\"const-meta\" id=\"const-meta\">\u2014</div>\n <div class=\"const-hint\">scroll to zoom \u00B7 drag to pan \u00B7 click a node</div>\n <div class=\"const-empty\" id=\"const-empty\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/></svg>\n <div class=\"ce-title\">No memories yet</div>\n <div class=\"ce-msg\">Attach Shadow Brain to your agents and let them work \u2014 memories will appear here as a living constellation.</div>\n </div>\n <div class=\"const-drawer\" id=\"const-drawer\">\n <button class=\"const-drawer-close\" onclick=\"closeConstDrawer()\" aria-label=\"Close\">\u00D7</button>\n <div id=\"const-drawer-body\"></div>\n </div>\n <div class=\"const-tip\" id=\"const-tip\"></div>\n </div>\n </div>\n\n <!-- Analytics strip -->\n <div class=\"grid cols-4 mt-lg\">\n <div class=\"card stat-card accent\"><div class=\"label\">Total Memories</div><div class=\"value accent\" id=\"intel-total\">\u2014</div><div class=\"trend\" id=\"intel-total-sub\">in the brain</div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">Source Coverage</div><div class=\"value\" id=\"intel-coverage\">\u2014</div><div class=\"meter mt\"><div class=\"fill\" id=\"intel-coverage-meter\" style=\"width:0%\"></div></div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">Fresh (24h)</div><div class=\"value\" id=\"intel-recent\">\u2014</div><div class=\"trend up\" id=\"intel-recent-sub\">new memories</div></div>\n <div class=\"card stat-card accent\"><div class=\"label\">High Importance</div><div class=\"value\" id=\"intel-highimp\">\u2014</div><div class=\"trend\" id=\"intel-highimp-sub\">score \u2265 0.75</div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Agents by Contribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-intel-agents\"></canvas></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Memory Categories</div></div><div class=\"chart-wrap\"><canvas id=\"chart-intel-cats\"></canvas></div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Tags</div></div><div id=\"intel-tags\" class=\"tag-cloud\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Recent Memories</div></div><div id=\"intel-recent-list\" class=\"stack\" style=\"max-height:260px;overflow-y:auto\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 LIVE GRAPH \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- WW2BR BRAIN -->\n <section class=\"section\" id=\"tab-ww2br\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">WW2BR Brain View</h1>\n <div class=\"section-subtitle\">Live project memory, recall, and integration status for the current game-development workflow</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn primary\" onclick=\"refreshWW2BR()\">Refresh</button>\n </div>\n </div>\n\n <div class=\"workflow-strip\">\n <div>\n <div class=\"field\" style=\"margin:0\">\n <label>Active project path</label>\n <input class=\"workflow-path\" id=\"ww2-project-path\" value=\"D:\\New Game\"/>\n </div>\n </div>\n <button class=\"btn\" onclick=\"refreshWW2BR()\">Load</button>\n </div>\n\n <div id=\"ww2-warnings\" class=\"warning-stack\"></div>\n\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\">\n <div class=\"label\">Global Memories</div>\n <div class=\"value accent\" id=\"ww2-stat-memories\">0</div>\n <div class=\"trend\" id=\"ww2-stat-scope\">project scoped</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Brain Projects</div>\n <div class=\"value\" id=\"ww2-stat-projects\">0</div>\n <div class=\"trend\">known roots</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Agent Hooks</div>\n <div class=\"value\" id=\"ww2-stat-hooks\">0</div>\n <div class=\"trend\">wired for this path</div>\n </div>\n <div class=\"card stat-card\">\n <div class=\"label\">Hit Rate</div>\n <div class=\"value\" id=\"ww2-stat-hit\">0%</div>\n <div class=\"trend\" id=\"ww2-stat-sync\">last sync unknown</div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Memory Quality</div></div>\n <div id=\"ww2-quality\" class=\"stack\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\">\n <div class=\"card-title\">Context Continuity Pack</div>\n <button class=\"btn sm\" onclick=\"openWW2ResumePack()\">Markdown</button>\n </div>\n <div id=\"ww2-resume-pack\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card span-2\">\n <div class=\"card-header\">\n <div class=\"card-title\">Recent Brain Timeline</div>\n <button class=\"btn sm\" onclick=\"refreshWW2BR()\">Refresh</button>\n </div>\n <div id=\"ww2-timeline\" class=\"brain-feed\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Integrations</div></div>\n <div id=\"ww2-agents\" class=\"ops-agent-grid\"></div>\n </div>\n </div>\n\n <div class=\"grid cols-3 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Memory Categories</div></div>\n <div id=\"ww2-categories\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Recall Query</div></div>\n <div class=\"grid cols-3\">\n <div class=\"field span-2\"><label>Query</label><input id=\"ww2-query\" placeholder=\"Gate 2, operator proof, lobby blocker\"/></div>\n <div class=\"field\"><label>Category</label><input id=\"ww2-category\" placeholder=\"optional\"/></div>\n </div>\n <div class=\"flex gap\">\n <button class=\"btn primary\" onclick=\"runWW2Recall()\">Recall</button>\n <button class=\"btn\" onclick=\"setWW2Query('Gate 2 visual proof')\">Gate 2</button>\n <button class=\"btn\" onclick=\"setWW2Query('operator hands rifle hold')\">Operator</button>\n <button class=\"btn\" onclick=\"setWW2Query('lobby verification blockers')\">Lobby</button>\n </div>\n <div id=\"ww2-recall-results\" class=\"brain-feed mt\"></div>\n </div>\n </div>\n </section>\n\n <section class=\"section\" id=\"tab-graph\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Live Agent Network</h1>\n <div class=\"section-subtitle\">Real-time signal flow: brain \u2194 agents \u2194 sub-agents</div>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <div class=\"graph-wrap\"><canvas id=\"graph-canvas\" width=\"1600\" height=\"600\"></canvas></div>\n <div style=\"padding:10px 14px;display:flex;gap:16px;flex-wrap:wrap;font-size:11px;color:var(--text-2)\">\n <span><span style=\"color:var(--success)\">\u25CF</span> memory-write</span>\n <span><span style=\"color:var(--brand-cyan)\">\u25CF</span> subagent-spawn</span>\n <span><span style=\"color:var(--brand-purple)\">\u25CF</span> dream</span>\n <span><span style=\"color:var(--danger)\">\u25CF</span> collision</span>\n <span><span style=\"color:var(--warning)\">\u25CF</span> firewall-block</span>\n <span id=\"graph-meta\" style=\"margin-left:auto;color:var(--text-3)\"></span>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 AGENTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-agents\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Agent Connections</h1>\n <div class=\"section-subtitle\">Connect or disconnect Shadow Brain hooks per agent \u00B7 per-agent native mechanisms</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn success\" onclick=\"attachAll()\">Attach All</button>\n <button class=\"btn danger\" onclick=\"detachAll()\">Detach All</button>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\">\n <thead><tr><th>Agent</th><th>Detected</th><th>Hook Status</th><th>Install Path</th><th style=\"text-align:right\">Action</th></tr></thead>\n <tbody id=\"agents-table\"></tbody>\n </table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CHAT WITH BRAIN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-chat\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>Chat with Your Brain</h1>\n <div class=\"section-subtitle\">Ask anything \u2014 Shadow Brain retrieves relevant memories via semantic search and cites them. Uses your configured lead model.</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"newChat()\">New Chat</button>\n <button class=\"btn danger\" onclick=\"clearAllChats()\">Clear All</button>\n </div>\n </div>\n <div class=\"grid\" style=\"grid-template-columns: 260px 1fr; gap: 14px\">\n <div class=\"card\" style=\"height: calc(100vh - 220px); overflow-y: auto; padding: 12px\">\n <div class=\"card-title\" style=\"margin-bottom: 10px\">Conversations</div>\n <div id=\"chat-conversations\"></div>\n </div>\n <div class=\"card\" style=\"height: calc(100vh - 220px); display: flex; flex-direction: column; padding: 0\">\n <div id=\"chat-messages\" style=\"flex: 1; overflow-y: auto; padding: 16px\"></div>\n <div style=\"border-top: 1px solid var(--border); padding: 12px; display: flex; gap: 8px\">\n <input id=\"chat-input\" placeholder=\"Ask your brain \u2014 e.g. 'What did Cursor decide about bcrypt?'\" style=\"flex:1;background:var(--bg-elev-1);border:1px solid var(--border);border-radius:8px;padding:10px 14px;color:var(--text);font-size:13px\" onkeydown=\"if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendChat()}\"/>\n <button class=\"btn primary\" onclick=\"sendChat()\" id=\"chat-send-btn\">Send</button>\n </div>\n <div class=\"mono\" style=\"padding: 4px 16px 10px; font-size: 10px; color: var(--text-3)\" id=\"chat-status\">idle</div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 MEMORY BROWSER \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-memory\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Memory Browser</h1>\n <div class=\"section-subtitle\">Full-text search plus live memory quality, provenance, freshness, and agent coverage</div>\n </div>\n </div>\n <div class=\"card\">\n <div class=\"grid cols-4\">\n <div class=\"field\"><label>Search query</label><input id=\"mem-query\" placeholder=\"e.g. auth, react, bcrypt\u2026\"/></div>\n <div class=\"field\"><label>Agent</label><select id=\"mem-agent\"><option value=\"\">all agents</option><option>claude-code</option><option>cursor</option><option>codex</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Category</label><input id=\"mem-category\" placeholder=\"e.g. security\"/></div>\n <div class=\"field\"><label>Min importance</label><input id=\"mem-importance\" type=\"number\" min=\"0\" max=\"1\" step=\"0.1\" value=\"0\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"searchMemory()\">Search</button>\n </div>\n <div class=\"card mt\">\n <table class=\"table\"><thead><tr><th>ID</th><th>Agent</th><th>Category</th><th>Content</th><th>Importance</th><th>Created</th></tr></thead>\n <tbody id=\"mem-results\"></tbody>\n </table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 LIVE BRAIN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <!-- MEMORY SEEDER -->\n <section class=\"section\" id=\"tab-seeder\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Memory Seeder</h1>\n <div class=\"section-subtitle\">Research drafts, approval gates, and source-backed global brain writes</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"refreshSeedDrafts()\">Refresh</button>\n </div>\n </div>\n\n <div class=\"grid cols-3\">\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Seed Research Draft</div></div>\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Project path</label><input id=\"seed-project-path\" placeholder=\"C:\\path\\to\\project\"/></div>\n <div class=\"field\"><label>Topic</label><input id=\"seed-topic\" placeholder=\"memory routing, architecture, release, debugging\"/></div>\n </div>\n <div class=\"field\"><label>Objective</label><input id=\"seed-objective\" placeholder=\"what the next agent must learn and reuse\"/></div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Research agent</label><select id=\"seed-research-agent\"><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Approver</label><select id=\"seed-approver-agent\"><option>user</option><option>codex</option><option>claude-code</option><option>cursor</option><option>cline</option><option>copilot</option><option>windsurf</option><option>kilo-code</option><option>roo-code</option><option>aider</option><option>opencode</option></select></div>\n <div class=\"field\"><label>Approval mode</label><select id=\"seed-approval-mode\"><option>user</option><option>agent</option><option>dual</option></select></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Category</label><input id=\"seed-category\" value=\"project.research\"/></div>\n <div class=\"field\"><label>Tags</label><input id=\"seed-tags\" placeholder=\"enterprise,memory,release\"/></div>\n <div class=\"field\"><label>Importance</label><input id=\"seed-importance\" type=\"number\" min=\"0.05\" max=\"1\" step=\"0.05\" value=\"0.78\"/></div>\n </div>\n <div class=\"field\"><label>Sources</label><textarea id=\"seed-sources\" rows=\"2\" placeholder=\"README.md, CHANGELOG.md, docs/launch/RELEASE_NOTES_v6.0.3.md\"></textarea></div>\n <div class=\"field\"><label>Research notes</label><textarea id=\"seed-content\" rows=\"4\" placeholder=\"facts, constraints, tradeoffs, exact commands, source-backed decisions\"></textarea></div>\n <div class=\"flex gap\">\n <button class=\"btn primary\" onclick=\"quickSeed()\" title=\"Create, approve, and save to the brain in one step\">\u26A1 Quick Save</button>\n <button class=\"btn\" onclick=\"createSeedDraft()\" title=\"Create a draft that waits for approval\">Create Draft (review first)</button>\n <button class=\"btn\" onclick=\"copyLivePathToSeeder()\">Use Live Brain Path</button>\n </div>\n <div class=\"section-subtitle\" style=\"margin-top:8px\">Quick Save is the fast, one-click path. Use \"Create Draft\" when you want a review/approval step before it's saved.</div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Seeder Status</div></div>\n <div id=\"seed-status\" class=\"stack\"></div>\n </div>\n </div>\n\n <div class=\"card mt-lg\">\n <div class=\"card-header\">\n <div class=\"card-title\">Approval Queue</div>\n <button class=\"btn sm\" onclick=\"refreshSeedDrafts()\">Refresh</button>\n </div>\n <div id=\"seed-drafts\" class=\"brain-feed\"></div>\n </div>\n </section>\n\n <section class=\"section\" id=\"tab-livebrain\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Live Brain</h1>\n <div class=\"section-subtitle\">Memory health, source coverage, recall eval, and agent briefing for the current workspace</div>\n </div>\n <div class=\"section-actions\">\n <button class=\"btn\" onclick=\"openSeederForLiveBrain()\">Seed Memory</button>\n <button class=\"btn primary\" onclick=\"refreshLiveBrain()\">Refresh</button>\n </div>\n </div>\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\"><div class=\"label\">Total Memories</div><div class=\"value\" id=\"mem-total\">0</div><div class=\"trend\" id=\"mem-sampled\">sampled</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Source Coverage</div><div class=\"value\" id=\"mem-source\">0%</div><div class=\"trend\">evidence-backed records</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Fresh 7 Days</div><div class=\"value\" id=\"mem-fresh\">0</div><div class=\"trend\">recent learning</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Seed Queue</div><div class=\"value\" id=\"mem-seed-open\">0</div><div class=\"trend\" id=\"mem-seed-sub\">approval queue</div></div>\n </div>\n <div class=\"grid cols-3 mt\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Categories</div></div><div id=\"mem-categories\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Agent Coverage</div></div><div id=\"mem-agents\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Top Tags</div></div><div id=\"mem-tags\"></div></div>\n </div>\n <div class=\"card mt\">\n <div class=\"field-row\">\n <div class=\"field\"><label>Project path</label><input id=\"live-project-path\" placeholder=\"C:\\path\\to\\project\"/></div>\n <div class=\"field\"><label>Eval queries</label><input id=\"live-queries\" placeholder=\"current blocker|do not repeat|verified command\"/></div>\n </div>\n </div>\n <div class=\"grid cols-4 mt\">\n <div class=\"card stat-card\"><div class=\"label\">Memories</div><div class=\"value\" id=\"live-memories\">0</div><div class=\"trend\" id=\"live-scope\">scope</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Quality</div><div class=\"value\" id=\"live-quality\">0</div><div class=\"trend\">average score</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Sources</div><div class=\"value\" id=\"live-sources\">0%</div><div class=\"trend\">coverage</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Recall</div><div class=\"value\" id=\"live-recall\">0</div><div class=\"trend\">avg hits/query</div></div>\n </div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Recommended Actions</div></div>\n <div id=\"live-actions\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Recall Eval</div></div>\n <div id=\"live-eval\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Top Memories</div></div>\n <div id=\"live-top\" class=\"stack\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Briefing</div></div>\n <pre class=\"mono\" id=\"live-brief\" style=\"max-height:360px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px;white-space:pre-wrap\"></pre>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 ACTIVITY LOG \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-activity\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Activity Log <span class=\"live-badge\" id=\"act-live-badge\" title=\"Streaming live over WebSocket\">LIVE</span></h1>\n <div class=\"section-subtitle\">Realtime stream: memory writes, SABB spawns, collisions, dreams, seeds, consolidations, firewall blocks</div>\n </div>\n <div class=\"section-actions\">\n <input id=\"act-search\" class=\"act-search\" placeholder=\"Search activity\u2026\" oninput=\"renderActivityTable()\">\n <button class=\"btn\" id=\"act-pause-btn\" onclick=\"toggleActivityPause()\" title=\"Pause/resume the live stream\">Pause</button>\n <button class=\"btn danger\" onclick=\"clearActivity()\" title=\"Clear the activity view\">Clear</button>\n <button class=\"btn\" onclick=\"refreshActivity()\">Refresh</button>\n </div>\n </div>\n <div class=\"filter-chips\" id=\"act-chips\">\n <button class=\"chip active\" data-type=\"\" onclick=\"setActivityFilter(this,'')\">All</button>\n <button class=\"chip\" data-type=\"memory-write\" onclick=\"setActivityFilter(this,'memory-write')\">Memory</button>\n <button class=\"chip\" data-type=\"dream\" onclick=\"setActivityFilter(this,'dream')\">Dreams</button>\n <button class=\"chip\" data-type=\"seed-draft\" onclick=\"setActivityFilter(this,'seed-draft')\">Seeds</button>\n <button class=\"chip\" data-type=\"collision\" onclick=\"setActivityFilter(this,'collision')\">Collisions</button>\n <button class=\"chip\" data-type=\"subagent-spawn\" onclick=\"setActivityFilter(this,'subagent-spawn')\">Sub-Agents</button>\n <button class=\"chip\" data-type=\"consolidation\" onclick=\"setActivityFilter(this,'consolidation')\">Consolidation</button>\n </div>\n <div class=\"grid cols-2 mb\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Events / min</div><span class=\"pill cyan\" id=\"act-rate-pill\">0 / min</span></div>\n <div class=\"chart-wrap\" style=\"height:120px\"><canvas id=\"act-spark\"></canvas></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Stream Stats</div></div>\n <div class=\"row\"><span class=\"k\">Events buffered</span><span class=\"v\" id=\"act-stat-total\">0</span></div>\n <div class=\"row\"><span class=\"k\">Live status</span><span class=\"v good\" id=\"act-stat-live\">streaming</span></div>\n <div class=\"row\"><span class=\"k\">Last event</span><span class=\"v\" id=\"act-stat-last\">\u2014</span></div>\n </div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\"><thead><tr><th>Time</th><th>Source</th><th>Type</th><th>Detail</th></tr></thead><tbody id=\"activity-table\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 SABB \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-sabb\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Sub-Agent Brain Bridge</h1>\n <div class=\"section-subtitle\">Context slivers + quarantine for Claude Task / Cursor Composer / CrewAI / LangGraph / AutoGen</div>\n </div>\n </div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">SABB Stats</div></div>\n <div id=\"sabb-stats\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Compute Context Sliver</div></div>\n <div class=\"field\"><label>Parent agent</label><select id=\"sabb-parent\"><option>claude-code</option><option>cursor</option><option>cline</option><option>codex</option></select></div>\n <div class=\"field\"><label>Framework</label><select id=\"sabb-framework\"><option>claude-code-task</option><option>cursor-composer</option><option>cline-substep</option><option>crewai</option><option>langgraph</option><option>autogen</option><option>generic</option></select></div>\n <div class=\"field\"><label>Task description</label><input id=\"sabb-task\" placeholder=\"e.g. refactor auth middleware\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Token budget</label><input id=\"sabb-budget\" value=\"300\"/></div>\n <div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"computeSliver()\" style=\"width:100%\">Generate</button></div>\n </div>\n <pre class=\"mono\" id=\"sabb-sliver-output\" style=\"margin-top:10px;max-height:220px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Quarantined Memories</div></div>\n <table class=\"table\"><thead><tr><th>ID</th><th>Sub-Agent</th><th>Category</th><th>Conf.</th><th>Content</th><th style=\"text-align:right\">Action</th></tr></thead><tbody id=\"sabb-quarantine\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CAUSAL CHAINS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-causal\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div><h1 class=\"section-title\">Causal Memory Chains</h1><div class=\"section-subtitle\">Trace the full causal history of any decision</div></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Stats</div></div>\n <div id=\"causal-stats\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">Trace Ancestors / Descendants</div></div>\n <div class=\"field\"><label>Memory ID</label><input id=\"causal-mem-id\" placeholder=\"abc123\u2026\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Max depth</label><input id=\"causal-depth\" value=\"8\"/></div>\n <div class=\"field\"><label> </label><div style=\"display:flex;gap:6px\"><button class=\"btn primary\" onclick=\"traceCausal()\" style=\"flex:1\">Ancestors</button><button class=\"btn\" onclick=\"influenceCausal()\" style=\"flex:1\">Descendants</button></div></div>\n </div>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Causal Chain Graph</div></div>\n <div class=\"dag-wrap\" id=\"causal-dag-wrap\" style=\"margin:0 14px 14px\">\n <canvas id=\"causal-dag\"></canvas>\n <div class=\"dag-empty\" id=\"causal-dag-empty\">Enter a memory ID and trace ancestors or descendants to render its causal chain.</div>\n </div>\n </div>\n <div class=\"card mt-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Record a Causal Link</div></div>\n <div class=\"grid cols-3\">\n <div class=\"field\"><label>Effect ID</label><input id=\"causal-effect\"/></div>\n <div class=\"field\"><label>Cause ID</label><input id=\"causal-cause\"/></div>\n <div class=\"field\"><label>Rationale (optional)</label><input id=\"causal-rationale\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"linkCausal()\">Record Link</button>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 COLLISION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-collision\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Agent Collision Detective</h1><div class=\"section-subtitle\">Real-time conflict detection across agent edit intents</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Stats</div></div><div id=\"collision-stats\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Active Alerts</div></div><div id=\"collision-alerts\"></div></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Active Edit Intents</div></div>\n <table class=\"table\"><thead><tr><th>Agent</th><th>File</th><th>Lines</th><th>Intent</th><th>Expires</th></tr></thead><tbody id=\"collision-intents\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 DREAM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-dream\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div><h1 class=\"section-title\">Dream Engine</h1><div class=\"section-subtitle\">Background idle reflection \u2014 local-first via Ollama</div></div>\n <div class=\"section-actions\"><button class=\"btn primary\" onclick=\"runDream()\">Run Cycle</button><button class=\"btn\" onclick=\"startDreamLoop()\">Start Loop</button></div>\n </div>\n <div class=\"grid cols-3\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Stats</div></div><div id=\"dream-stats\"></div></div>\n <div class=\"card span-2\"><div class=\"card-header\"><div class=\"card-title\">Dream Type Distribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-dreams\"></canvas></div></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Recent Dreams</div></div>\n <table class=\"table\"><thead><tr><th>Type</th><th>Content</th><th>Conf.</th><th>Action</th><th></th></tr></thead><tbody id=\"dream-list\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 REPUTATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-reputation\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Agent Reputation Ledger</h1><div class=\"section-subtitle\">Ed25519-signed decision receipts \u00B7 portable trust score</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Ledger Stats</div></div><div id=\"reputation-stats\"></div></div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Generate Badge</div></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Agent</label><select id=\"rep-agent\"><option>claude-code</option><option>cursor</option><option>codex</option><option>cline</option></select></div>\n <div class=\"field\"><label>Version</label><input id=\"rep-ver\" placeholder=\"4.7\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"genBadge()\">Generate</button>\n <pre class=\"mono\" id=\"rep-badge-output\" style=\"margin-top:10px;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n <div class=\"card mt-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Agent Trust</div></div>\n <div id=\"rep-trust-grid\" class=\"trust-grid\"></div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Agent Leaderboard</div></div>\n <table class=\"table\"><thead><tr><th>Agent</th><th>Accuracy</th><th>Decisions</th><th>Correct/Incorrect/Partial</th></tr></thead><tbody id=\"rep-leaderboard\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 DEBATE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-debate\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Swarm Debate Protocol</h1><div class=\"section-subtitle\">Pro / con / arbiter multi-agent debate for critical decisions</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Question</label><input id=\"debate-question\" placeholder=\"e.g. Redis or Postgres for session storage?\"/></div>\n <div class=\"field\"><label>Context</label><textarea id=\"debate-context\" rows=\"2\" placeholder=\"e.g. 10k users, read-heavy workload\"></textarea></div>\n <div class=\"field-row\"><div class=\"field\"><label>Turns per side</label><input id=\"debate-turns\" value=\"2\"/></div><div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"runDebate()\" style=\"width:100%\">Run Debate</button></div></div>\n <pre class=\"mono\" id=\"debate-output\" style=\"margin-top:12px;max-height:420px;overflow:auto;background:var(--bg-elev-1);padding:12px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PREMORTEM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-premortem\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Pre-Mortem Assistant</h1><div class=\"section-subtitle\">Surface past failures from YOUR project before significant tasks</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Task description</label><input id=\"pm-task\" placeholder=\"e.g. migrate auth to passkeys\"/></div>\n <button class=\"btn primary\" onclick=\"runPremortem()\">Run Pre-Mortem</button>\n <div id=\"pm-output\" style=\"margin-top:14px\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 ATTENTION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-attention\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Attention Heatmap</h1><div class=\"section-subtitle\">See which memories shaped a decision \u2014 weighted attribution</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Decision text</label><input id=\"att-decision\" placeholder=\"e.g. use parameterized SQL queries\"/></div>\n <div class=\"field\"><label>Candidate memory IDs (comma-separated, optional \u2014 blank uses recent)</label><input id=\"att-memories\" placeholder=\"id1, id2, id3\"/></div>\n <button class=\"btn primary\" onclick=\"runAttention()\">Compute Attention</button>\n <div id=\"att-output\" style=\"margin-top:14px\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 TOKENS \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-tokens\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Token Economy Engine</h1><div class=\"section-subtitle\">Cross-agent spend tracking \u00B7 savings suggestions \u00B7 routing advice</div></div></div>\n <div class=\"grid cols-4\">\n <div class=\"card stat-card\"><div class=\"label\">Current Agent</div><div class=\"value\" id=\"tok-agent\">\u2014</div><div class=\"trend\" id=\"tok-runtime\">runtime context</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Total Spend</div><div class=\"value\" id=\"tok-total\">$\u2014</div><div class=\"trend\">all historical records</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Last 7 Days</div><div class=\"value\" id=\"tok-recent\">$\u2014</div><div class=\"trend\">recent records only</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Total Calls</div><div class=\"value\" id=\"tok-calls\">\u2014</div><div class=\"trend\" id=\"tok-calls-sub\"></div></div>\n </div>\n <div class=\"grid cols-2 mt\">\n <div class=\"card stat-card\"><div class=\"label\">Monthly Projection</div><div class=\"value\" id=\"tok-proj\">$\u2014</div><div class=\"trend\">from last 7 days</div></div>\n <div class=\"card stat-card\"><div class=\"label\">Savings Available</div><div class=\"value\" style=\"color:var(--success)\" id=\"tok-save\">$\u2014</div><div class=\"trend up\">via smart routing</div></div>\n </div>\n <div class=\"warning-banner mt\" id=\"tok-runtime-note\"><strong>Accuracy</strong><span>Token records are historical. Current agent and model spend are shown separately.</span></div>\n <div class=\"grid cols-2 mt-lg\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Recent Spend by Agent</div></div><div class=\"chart-wrap\"><canvas id=\"chart-tokens-agent\"></canvas></div><div id=\"tok-agent-table\" class=\"mt\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Historical Spend by Model</div></div><div class=\"chart-wrap\"><canvas id=\"chart-tokens-model\"></canvas></div><div id=\"tok-model-table\" class=\"mt\"></div></div>\n <div class=\"card span-2\"><div class=\"card-header\"><div class=\"card-title\">Savings Suggestions</div></div><div id=\"tok-suggestions\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FORGET \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-forget\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Forgetting Curve + Sleep Consolidation</h1><div class=\"section-subtitle\">Ebbinghaus-inspired natural decay \u00B7 tier promotion</div></div><div class=\"section-actions\"><button class=\"btn primary\" onclick=\"runForget()\">Run Consolidation Cycle</button></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Last Cycle</div></div><div id=\"forget-output\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Memory Strength Distribution</div></div><div class=\"chart-wrap\"><canvas id=\"chart-forget\"></canvas></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FORMAL \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-formal\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Formal Verification Bridge</h1><div class=\"section-subtitle\">Natural-language rules \u2192 ESLint / Semgrep / LSP diagnostics</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Generate Rule</div></div>\n <div class=\"field\"><label>Natural-language rule</label><textarea id=\"formal-text\" rows=\"3\" placeholder=\"e.g. always use parameterized SQL queries\"></textarea></div>\n <button class=\"btn primary\" onclick=\"genFormal()\">Generate</button>\n <pre class=\"mono\" id=\"formal-output\" style=\"margin-top:12px;max-height:220px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Exports</div></div>\n <div class=\"flex gap\"><button class=\"btn\" onclick=\"exportEslint()\">ESLint</button><button class=\"btn\" onclick=\"exportSemgrep()\">Semgrep YAML</button></div>\n <pre class=\"mono\" id=\"formal-export\" style=\"margin-top:12px;max-height:280px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CALIBRATION \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-calibration\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Confidence Calibration Monitor</h1><div class=\"section-subtitle\">Brier scores & trust weight per agent per category</div></div></div>\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Brier Score vs Trust Weight by Agent</div></div>\n <div class=\"chart-wrap\"><canvas id=\"chart-calibration\"></canvas></div>\n </div>\n <div class=\"card\" style=\"padding:0\">\n <table class=\"table\"><thead><tr><th>Agent</th><th>Category</th><th>Brier</th><th>Cal. Error</th><th>Overconf.</th><th>Trust Weight</th><th>Samples</th></tr></thead><tbody id=\"calibration-table\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 BRANCH \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-branch\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Branch Brains</h1><div class=\"section-subtitle\">Git-branch-aware memory context</div></div></div>\n <div class=\"grid cols-4\" id=\"branch-cards\"></div>\n <div class=\"card mt-lg\"><div class=\"card-header\"><div class=\"card-title\">Branch-Specific Categories</div></div><div id=\"branch-cats\" class=\"tag-cloud\"></div></div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PRIVACY \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-privacy\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Privacy & Safety</h1><div class=\"section-subtitle\">Air-Gap mode \u00B7 encryption \u00B7 hallucination quarantine</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Air-Gap Mode</div></div>\n <div id=\"airgap-state\"></div>\n <div class=\"flex gap mt\"><button class=\"btn success\" onclick=\"enableAirGap()\">Enable (strict)</button><button class=\"btn danger\" onclick=\"disableAirGap()\">Disable</button></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Hallucination Quarantine</div></div>\n <div id=\"quarantine-state\"></div>\n </div>\n </div>\n <div class=\"card mt-lg\" style=\"padding:0\">\n <div style=\"padding:14px 18px 6px\"><div class=\"card-title\">Pending Quarantine Queue</div></div>\n <table class=\"table\"><thead><tr><th>ID</th><th>Source</th><th>Claim</th><th>Reason</th><th style=\"text-align:right\">Action</th></tr></thead><tbody id=\"quarantine-list\"></tbody></table>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 VOICE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-voice\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Voice Mode</h1><div class=\"section-subtitle\">Process voice transcripts \u00B7 local Whisper-compatible</div></div></div>\n <div class=\"card\">\n <div class=\"field\"><label>Transcript</label><input id=\"voice-input\" placeholder=\"e.g. brain status\"/></div>\n <button class=\"btn primary\" onclick=\"sendVoice()\">Process</button>\n <pre class=\"mono\" id=\"voice-output\" style=\"margin-top:12px;background:var(--bg-elev-1);padding:10px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 GARDEN \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-garden\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Brain Garden</h1><div class=\"section-subtitle\">Living constellation visualization of the brain</div></div></div>\n <div class=\"card\">\n <canvas id=\"garden-canvas\" width=\"1200\" height=\"500\" style=\"width:100%;background:radial-gradient(ellipse at center, rgba(24,255,255,0.04), transparent);border-radius:8px;display:block\"></canvas>\n <div class=\"mono mt\" id=\"garden-stats\"></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 PR REVIEW \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-pr\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">PR Auto-Review</h1><div class=\"section-subtitle\">Generate GitHub PR comments from brain memories</div></div></div>\n <div class=\"card\">\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Repo (owner/name)</label><input id=\"pr-repo\" placeholder=\"theihtisham/agent-shadow-brain\"/></div>\n <div class=\"field\"><label>PR number</label><input id=\"pr-num\" placeholder=\"42\"/></div>\n </div>\n <div class=\"field\"><label>Changed files (comma-separated)</label><input id=\"pr-files\"/></div>\n <div class=\"field\"><label>Diff summary</label><textarea id=\"pr-diff\" rows=\"4\"></textarea></div>\n <button class=\"btn primary\" onclick=\"genPRReview()\">Generate Review</button>\n <pre class=\"mono\" id=\"pr-output\" style=\"margin-top:14px;max-height:400px;overflow:auto;background:var(--bg-elev-1);padding:14px;border-radius:6px\"></pre>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 TEAM \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-team\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Team Brain Sync</h1><div class=\"section-subtitle\">Peer-to-peer brain sync (WebRTC, no server)</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">My Peer Info</div></div><div id=\"team-self\"></div></div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Connected Peers</div></div><div id=\"team-peers\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 EXCHANGE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-exchange\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Brain Exchange</h1><div class=\"section-subtitle\">Export / import curated shareable brain slices</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Export Package</div></div>\n <div class=\"field\"><label>Name</label><input id=\"ex-name\" placeholder=\"react-patterns-v1\"/></div>\n <div class=\"field\"><label>Description</label><input id=\"ex-desc\"/></div>\n <div class=\"field-row\">\n <div class=\"field\"><label>Author</label><input id=\"ex-author\" value=\"anonymous\"/></div>\n <div class=\"field\"><label>Tags (csv)</label><input id=\"ex-tags\" placeholder=\"react, auth\"/></div>\n </div>\n <button class=\"btn primary\" onclick=\"exportPackage()\">Export</button>\n <div class=\"mono mt\" id=\"ex-out\"></div>\n </div>\n <div class=\"card\"><div class=\"card-header\"><div class=\"card-title\">Local Packages</div></div><div id=\"ex-list\"></div></div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 FEATURE TOGGLES \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-features\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Feature controls</h1><div class=\"section-subtitle\">Manage context, briefings, and the activity feed in the workspace.</div></div></div>\n <div class=\"card\"><div class=\"feature-grid\" id=\"feature-grid\"></div></div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 MODELS & INTELLIGENCE \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-models\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\">\n <div>\n <h1 class=\"section-title\">Models & Brain Intelligence</h1>\n <div class=\"section-subtitle\">Route the Hive Mind through Ollama \u00B7 Anthropic \u00B7 OpenAI \u00B7 OpenRouter \u00B7 Moonshot/Kimi \u00B7 Gemini \u00B7 DeepSeek \u00B7 Mistral \u2014 or reuse your existing agent tools' configuration</div>\n </div>\n <div class=\"section-actions\"><button class=\"btn\" onclick=\"refreshModels()\">Refresh</button></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Auto-Discovered Agent Configurations</div></div>\n <div class=\"mono mb\" style=\"color:var(--text-2)\">Shadow Brain detected these agent tools' configs on your machine \u2014 no need to re-enter API keys. Toggle \"Use agent-tool models\" below to let the brain borrow their intelligence.</div>\n <div id=\"discovered-agents\"></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Brain Intelligence Configuration</div></div>\n <div class=\"grid cols-2\">\n <div class=\"field\"><label>Lead Provider</label><select id=\"intel-provider\"></select></div>\n <div class=\"field\"><label>Lead Model</label><select id=\"intel-model\"></select></div>\n </div>\n <div class=\"feature-toggle mb\"><div class=\"icon\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/></svg></div><div class=\"meta\"><div class=\"name\">Use already-configured agent-tool models</div><div class=\"desc\">Reuse Claude Code / Cursor / Codex / Cline intelligence \u2014 no new API keys needed</div></div><label class=\"switch\"><input type=\"checkbox\" id=\"intel-agent-proxy\"><span class=\"slider\"></span></label></div>\n <div class=\"feature-toggle\"><div class=\"icon\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"13 2 3 14 12 14 11 22 21 10 12 10 13 2\"/></svg></div><div class=\"meta\"><div class=\"name\">Prefer Local-First (Ollama)</div><div class=\"desc\">Route everything through Ollama when possible \u00B7 zero cost \u00B7 offline-ready</div></div><label class=\"switch\"><input type=\"checkbox\" id=\"intel-local-first\"><span class=\"slider\"></span></label></div>\n <button class=\"btn primary mt\" onclick=\"saveIntelligence()\">Save Intelligence Config</button>\n <div class=\"mono mt\" id=\"intel-result\"></div>\n </div>\n\n <div class=\"card mb-lg\">\n <div class=\"card-header\"><div class=\"card-title\">Provider Configuration</div></div>\n <div class=\"grid cols-2\" id=\"providers-grid\"></div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">Custom Agent Paths (Hermes / OpenClaw / Nemoclaw / any)</div></div>\n <div class=\"mono mb\" style=\"color:var(--text-2)\">Point Shadow Brain at any agent's config file \u2014 we'll read the provider, model, and API key if present.</div>\n <div id=\"custom-agents-list\"></div>\n <div class=\"grid cols-3 mt\">\n <div class=\"field\"><label>Agent name</label><input id=\"ca-name\" placeholder=\"hermes / openclaw / nemoclaw / ...\"/></div>\n <div class=\"field\"><label>Config file path</label><input id=\"ca-path\" placeholder=\"C:\\Users\\you\\.hermes\\config.json\"/></div>\n <div class=\"field\"><label> </label><button class=\"btn primary\" onclick=\"addCustomAgent()\" style=\"width:100%\">Add</button></div>\n </div>\n </div>\n </section>\n\n <!-- \u2550\u2550\u2550\u2550 CONFIG \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n <section class=\"section\" id=\"tab-config\">\n <div class=\"accent-bar\"></div>\n <div class=\"section-header\"><div><h1 class=\"section-title\">Configuration</h1><div class=\"section-subtitle\">LLM provider \u00B7 API keys \u00B7 MCP server \u00B7 global settings</div></div></div>\n <div class=\"grid cols-2\">\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">LLM Provider</div></div>\n <div class=\"field\"><label>Provider</label><select id=\"cfg-provider\"><option value=\"ollama\">Ollama (local)</option><option value=\"anthropic\">Anthropic</option><option value=\"openai\">OpenAI</option><option value=\"gemini\">Gemini</option><option value=\"deepseek\">DeepSeek</option></select></div>\n <div class=\"field\"><label>API Key (blank = env var)</label><input type=\"password\" id=\"cfg-apikey\"/></div>\n <div class=\"field\"><label>Model</label><input id=\"cfg-model\" placeholder=\"claude-opus-4-7\"/></div>\n <div class=\"flex gap\"><button class=\"btn primary\" onclick=\"saveConfig()\">Save</button><button class=\"btn\" onclick=\"testConnection()\">Test</button></div>\n <div class=\"mono mt\" id=\"cfg-result\"></div>\n </div>\n <div class=\"card\">\n <div class=\"card-header\"><div class=\"card-title\">MCP Server</div></div>\n <div class=\"field\"><label>Port</label><input id=\"mcp-port\" value=\"7342\"/></div>\n <div class=\"field\"><label>Auth Token (optional)</label><input id=\"mcp-token\" type=\"password\"/></div>\n <div class=\"flex gap\"><button class=\"btn primary\" onclick=\"startMCP()\">Start</button><button class=\"btn danger\" onclick=\"stopMCP()\">Stop</button></div>\n <div class=\"mono mt\" id=\"mcp-result\"></div>\n </div>\n <div class=\"card span-2\">\n <div class=\"card-header\"><div class=\"card-title\">System Status</div></div>\n <div id=\"config-state\"></div>\n </div>\n </div>\n </section>\n\n </main>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Command Palette overlay (Cmd+K)\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<div class=\"cmd-overlay\" id=\"cmd-overlay\" onclick=\"if(event.target===this)closeCmdPalette()\">\n <div class=\"cmd-palette\">\n <input id=\"cmd-input\" placeholder=\"Search features, actions, memories\u2026\" autocomplete=\"off\"/>\n <div class=\"results\" id=\"cmd-results\"></div>\n </div>\n</div>\n\n<!-- Toast container -->\n<div class=\"toast-container\" id=\"toast-container\"></div>\n\n<!-- Log ticker -->\n<div class=\"log-ticker\">\n <div class=\"dot\"></div>\n <div class=\"scroll\" id=\"log-scroll\">Hive Mind initializing\u2026</div>\n <button class=\"icon-btn\" style=\"width:28px;height:24px;padding:0\" onclick=\"toggleLogPanel()\" title=\"Open log\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"18 15 12 9 6 15\"/></svg></button>\n</div>\n\n<!-- Expandable log panel -->\n<div class=\"log-panel\" id=\"log-panel\">\n <div class=\"head\">\n <div class=\"title\">LIVE ACTIVITY</div>\n <div class=\"flex gap\">\n <button class=\"btn sm ghost\" onclick=\"clearLogs()\">Clear</button>\n <button class=\"btn sm ghost\" onclick=\"toggleLogPanel()\">Close</button>\n </div>\n </div>\n <div id=\"log-panel-list\"></div>\n</div>\n\n<!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n Scripts\n \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n<script>\nconst FEATURES = [\n ['intelligence','Brain Map','Visual map of every memory, project and tool'],\n ['memory','Memories','Browse and search saved knowledge'],\n ['chat','Ask the Brain','Ask questions in plain language'],\n ['activity','Activity','Live feed of what the brain is doing'],\n ['agents','Connected Tools','Which AI tools are linked to the brain'],\n ['livebrain','Brain Health','Memory quality with tips to improve'],\n ['ww2br','Project Memory','Memory and status for one project'],\n ['graph','Live Network','Live animation of tools talking to the brain'],\n ['seeder','Add Knowledge','Research a topic and save it as verified knowledge'],\n ['garden','Memory Garden','Watch your memories grow over time'],\n ['sabb','Sub-Agents','Share the right context with helper agents'],\n ['causal','Decision Trails','Trace why a decision was made'],\n ['dream','Reflections','Idle-time review that finds patterns and lessons'],\n ['debate','Debates','Multiple AI viewpoints argue a tough decision'],\n ['premortem','Risk Check','See what went wrong last time before a big task'],\n ['branch','Branch Memory','Memory that follows your git branch'],\n ['forget','Memory Cleanup','Old memories fade, important ones stay'],\n ['collision','Conflicts','Catch two tools editing the same code'],\n ['reputation','Trust Scores','How reliable each AI tool has been'],\n ['calibration','Accuracy','How well confidence matches being right'],\n ['attention','Focus Map','Which memories shaped a decision'],\n ['formal','Code Rules','Turn plain rules into linter checks'],\n ['privacy','Privacy & Safety','Stay offline and block risky actions'],\n ['tokens','Cost & Usage','Track AI spend and find savings'],\n ['pr','Code Review','Generate a pull-request review'],\n ['voice','Voice','Talk to the brain hands-free'],\n ['team','Team Sync','Share memory with teammates'],\n ['exchange','Share Brain','Export or import memory packs'],\n ['features','Features','Turn individual features on or off'],\n ['models','AI Models','Choose which AI models the brain uses'],\n ['config','Settings','Set up and adjust Shadow Brain'],\n ['overview','Home','Your starting point and summary'],\n];\n\nlet ws = null;\nlet state = { hive: null, lastRefreshAt: 0 };\nlet charts = {};\n\n// \u2500\u2500\u2500\u2500 Tab switching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.nav-item').forEach(el => el.addEventListener('click', () => switchTab(el.dataset.tab, el.dataset.title)));\nfunction switchTab(id, title) {\n document.querySelectorAll('.nav-item').forEach(el => el.classList.toggle('active', el.dataset.tab === id));\n document.querySelectorAll('.section').forEach(el => el.classList.toggle('active', el.id === 'tab-' + id));\n if (window.innerWidth <= 768) document.getElementById('sidebar').classList.remove('open');\n onTabOpen(id);\n animateSectionIn(id);\n}\n// Subtle staggered card entrance on real tab switches. Guarded so it no-ops\n// if GSAP's CDN failed to load. NOT called from the 5s refresh loop, so cards\n// don't re-fade on every poll.\nfunction animateSectionIn(id) {\n if (!window.gsap) return;\n const sec = document.getElementById('tab-' + id);\n if (!sec) return;\n const cards = sec.querySelectorAll('.card');\n if (!cards.length) return;\n try { window.gsap.from(cards, { opacity: 0, y: 12, duration: 0.34, stagger: 0.035, ease: 'power2.out', clearProps: 'opacity,transform' }); } catch (e) {}\n}\nfunction onTabOpen(tab) {\n const fn = { overview: refreshOverview, intelligence: refreshIntelligence, ww2br: refreshWW2BR, graph: startGraph, agents: refreshAgents, memory: () => { refreshMemoryIntelligence(); searchMemory(); }, seeder: refreshSeedDrafts, livebrain: refreshLiveBrain, chat: refreshChat, activity: refreshActivity,\n sabb: refreshSABB, causal: refreshCausal, collision: refreshCollision, dream: refreshDream, reputation: refreshReputation,\n tokens: refreshTokens, forget: refreshForget, formal: null, calibration: refreshCalibration, branch: refreshBranch,\n privacy: refreshPrivacy, garden: refreshGarden, team: refreshTeam, exchange: refreshExchange, features: refreshFeatures,\n models: refreshModels, config: refreshConfig }[tab];\n if (fn) fn();\n}\n\n// \u2500\u2500\u2500\u2500 Sidebar mobile \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleSidebar() { document.getElementById('sidebar').classList.toggle('open'); }\n\n// \u2500\u2500\u2500\u2500 Theme toggle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toggleTheme() {\n const html = document.documentElement;\n const newT = html.dataset.theme === 'light' ? 'dark' : 'light';\n html.dataset.theme = newT;\n try { localStorage.setItem('hive.theme', newT); } catch {}\n if (charts.tokens) updateChartColors();\n}\n(function initTheme(){\n try {\n const t = localStorage.getItem('hive.theme');\n // Default to light for better readability\n document.documentElement.dataset.theme = t || 'light';\n } catch { document.documentElement.dataset.theme = 'light'; }\n})();\n\n// \u2500\u2500\u2500\u2500 Simple / Advanced navigation mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction setMode(mode) {\n const m = mode === 'advanced' ? 'advanced' : 'simple';\n document.documentElement.dataset.navmode = m;\n try { localStorage.setItem('hive.navmode', m); } catch {}\n document.querySelectorAll('.mode-btn').forEach(b => b.classList.toggle('active', b.dataset.mode === m));\n // If we just hid the tab the user is on, gently return them to Home.\n if (m === 'simple') {\n const active = document.querySelector('.nav-item.active');\n if (active && active.closest('[data-adv]')) switchTab('overview', 'Home');\n }\n}\n(function initMode(){\n let m = 'simple';\n try { m = localStorage.getItem('hive.navmode') || 'simple'; } catch {}\n setMode(m);\n})();\n\n// \u2500\u2500\u2500\u2500 API helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function api(path, body) {\n try {\n const opts = body !== undefined ? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) } : {};\n const res = await fetch(path, opts);\n if (!res.ok) { toast('API error', res.status + ' ' + path, 'error'); return null; }\n const ct = res.headers.get('content-type') || '';\n return ct.includes('application/json') ? await res.json() : await res.text();\n } catch (err) { toast('Network error', err.message, 'error'); return null; }\n}\nfunction htmlEscape(value) {\n return String(value ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\nfunction fmtDate(value) {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? 'unknown' : d.toLocaleString();\n}\n// Reusable animated empty-state. cta = {label, onclick} or null.\n// title/msg are inserted as-is (allow pre-escaped entities) \u2014 author-controlled strings only.\nfunction emptyState(icon, title, msg, cta) {\n const icons = {\n shield: '<path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\"/>',\n check: '<polyline points=\"20 6 9 17 4 12\"/>',\n target: '<circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/>',\n 'git-branch': '<line x1=\"6\" y1=\"3\" x2=\"6\" y2=\"15\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/><path d=\"M18 9a9 9 0 0 1-9 9\"/>',\n brain: '<circle cx=\"12\" cy=\"12\" r=\"3\"/><circle cx=\"5\" cy=\"6\" r=\"2\"/><circle cx=\"19\" cy=\"6\" r=\"2\"/><circle cx=\"5\" cy=\"18\" r=\"2\"/><circle cx=\"19\" cy=\"18\" r=\"2\"/>',\n inbox: '<polyline points=\"22 12 16 12 14 15 10 15 8 12 2 12\"/><path d=\"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z\"/>',\n };\n const path = icons[icon] || icons.inbox;\n let btn = '';\n if (cta && cta.label) {\n const handler = cta.onclick ? ' onclick=\"' + cta.onclick + '\"' : '';\n btn = '<button class=\"btn primary es-cta\"' + handler + '>' + htmlEscape(cta.label) + '</button>';\n }\n return '<div class=\"empty-state\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' + path + '</svg><div class=\"es-title\">' + title + '</div><div class=\"es-msg\">' + msg + '</div>' + btn + '</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Toasts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction toast(title, body, variant='success') {\n const c = document.getElementById('toast-container');\n const icons = { success:'<path d=\"M20 6 9 17l-5-5\"/>', warn:'<path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>', error:'<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"/>', info:'<circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/><line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>' };\n const el = document.createElement('div');\n el.className = 'toast ' + variant;\n el.innerHTML = `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${icons[variant] || icons.info}</svg><div><div class=\"title\">${title}</div>${body ? '<div class=\"body\">' + body + '</div>' : ''}</div>`;\n c.appendChild(el);\n setTimeout(() => { el.classList.add('removing'); setTimeout(() => el.remove(), 400); }, 3800);\n}\n\n// \u2500\u2500\u2500\u2500 Animated counters \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst _counterState = {};\nfunction animateNum(id, target) {\n const el = document.getElementById(id);\n if (!el) return;\n const start = parseFloat((el.textContent || '0').replace(/[^d.-]/g, '')) || 0;\n if (start === target) { el.textContent = target.toLocaleString(); return; }\n if (_counterState[id]) cancelAnimationFrame(_counterState[id]);\n const startTime = performance.now();\n const duration = 650;\n const step = now => {\n const t = Math.min(1, (now - startTime) / duration);\n const eased = 1 - Math.pow(1 - t, 3);\n const val = start + (target - start) * eased;\n el.textContent = Math.round(val).toLocaleString();\n if (t < 1) _counterState[id] = requestAnimationFrame(step);\n else { el.textContent = target.toLocaleString(); delete _counterState[id]; }\n };\n _counterState[id] = requestAnimationFrame(step);\n}\nfunction animateMoney(id, target) {\n const el = document.getElementById(id);\n if (!el) return;\n const start = parseFloat((el.textContent || '0').replace(/[^d.-]/g, '')) || 0;\n if (Math.abs(start - target) < 0.001) { el.textContent = '$' + target.toFixed(2); return; }\n if (_counterState[id]) cancelAnimationFrame(_counterState[id]);\n const startTime = performance.now();\n const duration = 650;\n const step = now => {\n const t = Math.min(1, (now - startTime) / duration);\n const eased = 1 - Math.pow(1 - t, 3);\n const val = start + (target - start) * eased;\n el.textContent = '$' + val.toFixed(2);\n if (t < 1) _counterState[id] = requestAnimationFrame(step);\n else { el.textContent = '$' + target.toFixed(2); delete _counterState[id]; }\n };\n _counterState[id] = requestAnimationFrame(step);\n}\n\n// \u2500\u2500\u2500\u2500 Main refresh \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refresh() {\n const hive = await api('/api/v6/hive-status');\n if (!hive) return;\n state.hive = hive;\n state.lastRefreshAt = Date.now();\n\n animateNum('stat-agents', hive.totalAgentsConnected ?? 0);\n animateNum('stat-memories', hive.totalMemoriesStored ?? 0);\n animateNum('stat-dreams', hive.modules.dream?.totalDreams ?? 0);\n animateNum('stat-tools', hive.totalAgentsConnected ?? 0);\n document.getElementById('badge-sabb').textContent = hive.modules.sabb?.totalSpawns ?? 0;\n document.getElementById('badge-causal').textContent = hive.modules.causal?.totalLinks ?? 0;\n document.getElementById('badge-collision').textContent = hive.modules.collision?.collisionsDetected ?? 0;\n document.getElementById('badge-dream').textContent = hive.modules.dream?.totalDreams ?? 0;\n const activeTab = document.querySelector('.nav-item.active')?.dataset.tab;\n if (activeTab) onTabOpen(activeTab);\n}\n\n// \u2500\u2500\u2500\u2500 Overview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshOverview() {\n const hive = state.hive || await api('/api/v6/hive-status');\n if (!hive) return;\n animateNum('ov-agents', hive.totalAgentsConnected ?? 0);\n animateNum('ov-memories', hive.totalMemoriesStored ?? 0);\n animateNum('ov-subagents', hive.modules.sabb?.totalSpawns ?? 0);\n document.getElementById('ov-subagents-sub').textContent = `grad: ${hive.modules.sabb?.graduated ?? 0} \u00B7 quar: ${hive.modules.sabb?.quarantined ?? 0}`;\n const rt = hive.runtime || {};\n document.getElementById('ov-current-agent').textContent = rt.currentAgentDisplay || 'Unknown';\n document.getElementById('ov-current-agent-sub').textContent = rt.provider ? (rt.provider + (rt.model ? ' / ' + rt.model : '')) : 'using the brain right now';\n loadConstHeroStats();\n\n const moduleRows = [\n ['SABB spawns', hive.modules.sabb?.totalSpawns ?? 0, 'good'],\n ['Causal links', hive.modules.causal?.totalLinks ?? 0, 'good'],\n ['Collisions detected', hive.modules.collision?.collisionsDetected ?? 0, 'warn'],\n ['Dreams recorded', hive.modules.dream?.totalDreams ?? 0, 'good'],\n ['Reputation receipts', hive.modules.reputation?.totalReceipts ?? 0, 'good'],\n ['Formal rules', hive.modules.formalBridge?.totalRules ?? 0, 'good'],\n ];\n document.getElementById('ov-modules').innerHTML = moduleRows.map(([k, v, cls]) =>\n `<div class=\"row\"><span class=\"k\">${k}</span><span class=\"v ${cls || ''}\">${v}</span></div>`\n ).join('');\n\n const health = [\n ['Local-first', hive.localFirst ? 'ON' : 'OFF', hive.localFirst ? 'good' : 'warn'],\n ['Current agent', hive.runtime?.currentAgentDisplay || 'Unknown', hive.runtime?.currentAgentTool === 'codex' ? 'good' : ''],\n ['Air-gap', hive.modules.airGap?.enabled ? 'ENABLED' : 'disabled', hive.modules.airGap?.enabled ? 'good' : ''],\n ['Version', hive.version, ''],\n ['Monthly cost', '$' + (hive.modules.tokenEconomy?.monthlyProjectionUsd?.toFixed?.(2) ?? '0.00'), 'warn'],\n ['Cost saved/mo', '$' + (hive.modules.tokenEconomy?.savingsOpportunitiesUsd?.toFixed?.(2) ?? '0.00'), 'good'],\n ];\n document.getElementById('ov-health').innerHTML = health.map(([k, v, cls]) => `<div class=\"row\"><span class=\"k\">${k}</span><span class=\"v ${cls}\">${v}</span></div>`).join('');\n\n // Tokens chart\n const tok = hive.modules.tokenEconomy;\n if (tok) drawTokensChart(tok.recentByAgent || tok.byAgent || {});\n\n // Timeline\n const timeline = await api('/api/v6/activity-log?limit=15') ?? [];\n document.getElementById('ov-timeline').innerHTML = timeline.length\n ? timeline.slice(0, 10).map(e => `<div style=\"padding:5px 0;border-bottom:1px solid var(--border);display:flex;gap:8px\"><span style=\"color:var(--text-dim);flex-shrink:0\">${new Date(e.timestamp).toLocaleTimeString()}</span><span class=\"pill ${typeToPill(e.type)}\">${e.type}</span><span style=\"color:var(--text-2);font-size:11px\">${(e.detail||'').slice(0,90)}</span></div>`).join('')\n : '<div class=\"empty\">No activity yet</div>';\n}\nfunction typeToPill(type) {\n if (type === 'memory-write') return 'green';\n if (type === 'subagent-spawn') return 'cyan';\n if (type === 'collision') return 'red';\n if (type === 'quarantine') return 'yellow';\n if (type === 'seed-draft') return 'yellow';\n if (type === 'memory-seeded') return 'green';\n if (['dream','revisit','counterfactual','consolidation','pattern-discovery','contradiction'].includes(type)) return 'purple';\n return '';\n}\n\nfunction getWW2ProjectPath() {\n return document.getElementById('ww2-project-path')?.value?.trim() || 'D:\\\\New Game';\n}\nasync function refreshWW2BR() {\n const projectDir = getWW2ProjectPath();\n const data = await api('/api/v6/ww2br-ops?projectDir=' + encodeURIComponent(projectDir));\n if (!data) return;\n\n const stats = data.stats || {};\n const hooked = (data.agents || []).filter(a => a.hookInstalled).length;\n document.getElementById('badge-ww2br').textContent = data.recent?.length ?? 0;\n animateNum('ww2-stat-memories', stats.totalEntries ?? 0);\n animateNum('ww2-stat-projects', stats.totalProjects ?? 0);\n animateNum('ww2-stat-hooks', hooked);\n document.getElementById('ww2-stat-hit').textContent = Math.round((stats.hitRate ?? 0) * 100) + '%';\n document.getElementById('ww2-stat-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';\n document.getElementById('ww2-stat-sync').textContent = stats.lastSync ? 'sync ' + fmtDate(stats.lastSync) : 'last sync unknown';\n\n const warnings = data.warnings || [];\n document.getElementById('ww2-warnings').innerHTML = warnings.length\n ? warnings.map(w => '<div class=\"warning-banner\"><strong>Warning</strong><span>' + htmlEscape(w) + '</span></div>').join('')\n : '';\n\n document.getElementById('ww2-timeline').innerHTML = renderMemoryItems(data.recent || [], 'No memories found for this workflow yet.');\n document.getElementById('ww2-agents').innerHTML = renderWW2Agents(data.agents || []);\n document.getElementById('ww2-categories').innerHTML = renderWW2Categories(data.categories || {});\n document.getElementById('ww2-quality').innerHTML = renderWW2Quality(data.quality);\n document.getElementById('ww2-resume-pack').innerHTML = renderWW2ResumePack(data.resumePack);\n\n const query = document.getElementById('ww2-query');\n if (query && !query.value) query.value = 'Gate 2 visual proof';\n if (!document.getElementById('ww2-recall-results').innerHTML.trim()) {\n document.getElementById('ww2-recall-results').innerHTML = '<div class=\"empty-tight\">Run a recall query to inspect relevant brain memory.</div>';\n }\n}\nfunction renderMemoryItems(items, emptyMessage) {\n if (!items || !items.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return items.map(item => {\n const importancePct = item.importance !== undefined ? Math.round((item.importance || 0) * 100) + '%' : '';\n // v7.2.0: real blended recall confidence (recency + importance + corroboration + usage + relevance).\n const conf = item.confidence && typeof item.confidence.score === 'number' ? item.confidence : null;\n const confCls = conf ? (conf.level === 'high' ? 'green' : conf.level === 'medium' ? 'yellow' : 'red') : '';\n const confBadge = conf ? '<span class=\"pill ' + confCls + '\" title=\"Heuristic recall score (not probability of truth) ' + Math.round(conf.score * 100) + '% (' + htmlEscape(conf.level) + ') | relevance ' + conf.signals.relevance + ', creation-age recency ' + conf.signals.recency + ', importance ' + conf.signals.importance + ', matching-content agent reports ' + conf.signals.corroboration + ', usage ' + conf.signals.usage + '\">\u2737 ' + Math.round(conf.score * 100) + '%</span>' : '';\n const sources = item.sources || (item.metadata && item.metadata.sources) || [];\n const tags = item.tags || (item.metadata && item.metadata.tags) || [];\n const quality = item.qualityScore !== undefined ? '<span class=\"pill\">Q ' + htmlEscape(item.qualityScore) + '</span>' : '';\n const sourceLine = sources.length\n ? '<div class=\"content\" style=\"font-size:10.5px;color:var(--text-3);margin-top:6px\">Sources: ' + sources.slice(0, 3).map(s => '<code>' + htmlEscape(s) + '</code>').join(' \u00B7 ') + '</div>'\n : '<div class=\"content\" style=\"font-size:10.5px;color:var(--warning);margin-top:6px\">Sources: none recorded</div>';\n const tagLine = tags.length ? '<span class=\"pill\">' + tags.slice(0, 3).map(htmlEscape).join(', ') + '</span>' : '';\n return '<div class=\"memory-item\">' +\n '<div class=\"meta\">' +\n '<span class=\"pill cyan\">' + htmlEscape(item.agentTool || item.agent || 'brain') + '</span>' +\n '<span class=\"pill\">' + htmlEscape(item.category || 'memory') + '</span>' +\n confBadge +\n (importancePct ? '<span class=\"pill\" title=\"Author-assigned importance\">imp ' + importancePct + '</span>' : '') +\n quality +\n tagLine +\n '<span>' + fmtDate(item.createdAt || item.lastAccessed) + '</span>' +\n '</div>' +\n '<div class=\"content\">' + htmlEscape(item.content || item.detail || '').slice(0, 420) + '</div>' +\n sourceLine +\n '</div>';\n }).join('');\n}\nfunction renderWW2Quality(quality) {\n if (!quality) return '<div class=\"empty-tight\">No memory quality data.</div>';\n const sourcePct = Math.round((quality.sourceCoverage || 0) * 100);\n const scoreCls = quality.avgScore >= 75 ? 'good' : quality.avgScore >= 55 ? 'warn' : 'bad';\n return [\n '<div class=\"row\"><span class=\"k\">Average score</span><span class=\"v ' + scoreCls + '\">' + htmlEscape(quality.avgScore ?? 0) + '/100</span></div>',\n '<div class=\"row\"><span class=\"k\">Source coverage</span><span class=\"v ' + (sourcePct >= 70 ? 'good' : 'warn') + '\">' + sourcePct + '%</span></div>',\n '<div class=\"row\"><span class=\"k\">Stale memories</span><span class=\"v ' + (quality.stale ? 'warn' : 'good') + '\">' + htmlEscape(quality.stale || 0) + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Duplicate clusters</span><span class=\"v ' + (quality.duplicates?.length ? 'warn' : 'good') + '\">' + htmlEscape(quality.duplicates?.length || 0) + '</span></div>',\n ].join('');\n}\nfunction renderWW2ResumePack(pack) {\n if (!pack) return '<div class=\"empty-tight\">No resume pack generated.</div>';\n const warnings = pack.warnings && pack.warnings.length\n ? '<div class=\"warning-stack\" style=\"margin:10px 0 0\">' + pack.warnings.slice(0, 3).map(w => '<div class=\"warning-banner\"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'\n : '';\n const blockers = pack.blockers && pack.blockers.length\n ? '<div class=\"brain-feed mt\">' + renderMemoryItems(pack.blockers.slice(0, 3), 'No blockers in pack.') + '</div>'\n : '<div class=\"empty-tight\">No blockers in pack.</div>';\n const stats = pack.stats || {};\n return '<div class=\"grid cols-3\">' +\n '<div class=\"row\"><span class=\"k\">Docs</span><span class=\"v\">' + htmlEscape(stats.docCount ?? 0) + '</span></div>' +\n '<div class=\"row\"><span class=\"k\">Memories</span><span class=\"v\">' + htmlEscape(stats.memoryCount ?? 0) + '</span></div>' +\n '<div class=\"row\"><span class=\"k\">Quality</span><span class=\"v\">' + htmlEscape(stats.avgQualityScore ?? 0) + '/100</span></div>' +\n '</div>' +\n '<div class=\"mt\" style=\"font-size:12px;color:var(--text-2)\">Generated ' + htmlEscape(fmtDate(pack.generatedAt)) + '. Uses visible docs and stored memories only.</div>' +\n warnings +\n '<div class=\"mt\">' + blockers + '</div>';\n}\nfunction openWW2ResumePack() {\n const url = '/api/v6/resume-pack?format=markdown&projectDir=' + encodeURIComponent(getWW2ProjectPath());\n window.open(url, '_blank', 'noopener');\n}\nfunction renderWW2Agents(agents) {\n if (!agents.length) return '<div class=\"empty-tight\">No known agent integrations returned.</div>';\n return agents.map(agent => {\n const cls = agent.hookInstalled ? 'green' : agent.detected ? 'cyan' : '';\n const hook = agent.hookInstalled ? 'hooked' : 'not wired';\n const detected = agent.detected ? 'detected' : 'not found';\n return '<div class=\"ops-agent\">' +\n '<div class=\"name\">' + htmlEscape(agent.displayName || agent.name) + '</div>' +\n '<div class=\"status\">' +\n '<span class=\"pill ' + cls + '\">' + hook + '</span>' +\n '<span class=\"pill\">' + detected + '</span>' +\n '</div>' +\n '</div>';\n }).join('');\n}\nfunction renderWW2Categories(categories) {\n const rows = Object.entries(categories).sort((a, b) => b[1] - a[1]);\n if (!rows.length) return '<div class=\"empty-tight\">No categories yet.</div>';\n return rows.map(([name, count]) => '<div class=\"row\"><span class=\"k\">' + htmlEscape(name) + '</span><span class=\"v\">' + count + '</span></div>').join('');\n}\nfunction setWW2Query(query) {\n document.getElementById('ww2-query').value = query;\n runWW2Recall();\n}\nasync function runWW2Recall() {\n const resultsEl = document.getElementById('ww2-recall-results');\n const query = document.getElementById('ww2-query').value.trim();\n const category = document.getElementById('ww2-category').value.trim();\n resultsEl.innerHTML = '<div class=\"empty-tight\">Searching brain memory...</div>';\n const data = await api('/api/v6/ww2br-recall', {\n projectDir: getWW2ProjectPath(),\n query,\n category: category || undefined,\n limit: 12,\n });\n if (!data) return;\n const prefix = data.projectScoped ? '' : '<div class=\"warning-banner\"><strong>Fallback</strong><span>No project-scoped matches; showing global brain recall results.</span></div>';\n resultsEl.innerHTML = prefix + renderMemoryItems(data.results || [], 'No recall matches for that query.');\n}\n\n// \u2500\u2500\u2500\u2500 Charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction chartTheme() {\n const dark = document.documentElement.dataset.theme !== 'light';\n return { grid: dark ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.05)', text: dark ? '#a4afc4' : '#3d4864' };\n}\nfunction spendLabels(buckets) {\n return Object.keys(buckets || {}).sort((a, b) => (buckets[b]?.spendUsd || 0) - (buckets[a]?.spendUsd || 0));\n}\nfunction drawSpendBarChart(canvasId, chartKey, buckets) {\n const ctx = document.getElementById(canvasId);\n if (!ctx) return;\n const labels = spendLabels(buckets);\n const data = labels.map(l => buckets[l].spendUsd || 0);\n const t = chartTheme();\n if (charts[chartKey]) charts[chartKey].destroy();\n charts[chartKey] = new Chart(ctx, { type: 'bar', data: { labels, datasets: [{ label: 'Spend (USD)', data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderRadius: 6 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{display:false}}, scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text,callback:v=>'$'+Number(v).toFixed(2)}}} } });\n}\nfunction drawTokensChart(byAgent) {\n drawSpendBarChart('chart-tokens', 'tokens', byAgent || {});\n}\nfunction drawTokensAgentChart(byAgent) {\n drawSpendBarChart('chart-tokens-agent', 'tokensAgent', byAgent || {});\n}\nfunction drawTokensModelChart(byModel) {\n const ctx = document.getElementById('chart-tokens-model');\n if (!ctx) return;\n const labels = spendLabels(byModel);\n const data = labels.map(l => byModel[l].spendUsd || 0);\n const t = chartTheme();\n if (charts.tokensModel) charts.tokensModel.destroy();\n charts.tokensModel = new Chart(ctx, { type: 'doughnut', data: { labels, datasets: [{ data, backgroundColor: ['#18ffff','#a855f7','#ec4899','#10e68c','#ffb53f','#4fb6ff'], borderWidth:0 }] }, options: { responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{color:t.text,boxWidth:12}}} } });\n}\nfunction renderSpendRows(buckets, emptyMessage) {\n const labels = spendLabels(buckets).slice(0, 8);\n if (!labels.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return labels.map(label => {\n const b = buckets[label] || {};\n const last = b.lastSeen ? new Date(b.lastSeen).toLocaleString() : '\u2014';\n return `<div class=\"row\"><span class=\"k\">${htmlEscape(label)} <span class=\"tag-code\">${b.calls || 0} calls</span></span><span class=\"v\">$${Number(b.spendUsd || 0).toFixed(4)} <span class=\"mono\" style=\"color:var(--text-3)\">${last}</span></span></div>`;\n }).join('');\n}\nfunction drawDreamChart(byType) {\n const ctx = document.getElementById('chart-dreams');\n if (!ctx) return;\n const labels = Object.keys(byType);\n const data = labels.map(l => byType[l]);\n const t = chartTheme();\n if (charts.dreams) charts.dreams.destroy();\n charts.dreams = new Chart(ctx, { type:'polarArea', data:{labels,datasets:[{data,backgroundColor:['#18ffff88','#a855f788','#ec489988','#10e68c88','#ffb53f88']}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{labels:{color:t.text}}},scales:{r:{grid:{color:t.grid},ticks:{color:t.text,backdropColor:'transparent'}}}} });\n}\nfunction drawForgetChart(states) {\n const ctx = document.getElementById('chart-forget');\n if (!ctx) return;\n const buckets = { strong: 0, medium: 0, weak: 0, fading: 0 };\n for (const s of states || []) {\n if (s.currentStrength >= 0.8) buckets.strong++;\n else if (s.currentStrength >= 0.5) buckets.medium++;\n else if (s.currentStrength >= 0.2) buckets.weak++;\n else buckets.fading++;\n }\n const t = chartTheme();\n if (charts.forget) charts.forget.destroy();\n charts.forget = new Chart(ctx, { type:'bar', data:{labels:Object.keys(buckets),datasets:[{data:Object.values(buckets),backgroundColor:['#10e68c','#18ffff','#ffb53f','#ff5864'],borderRadius:4}]}, options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false}},scales:{x:{grid:{color:t.grid},ticks:{color:t.text}},y:{grid:{color:t.grid},ticks:{color:t.text}}}} });\n}\nfunction updateChartColors() { for (const k of Object.keys(charts)) { charts[k].destroy(); delete charts[k]; } refresh(); }\n\n// \u2500\u2500\u2500\u2500 SABB \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshSABB() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.sabb ?? {};\n document.getElementById('sabb-stats').innerHTML = `\n <div class=\"row\"><span class=\"k\">Total spawns</span><span class=\"v\">${s.totalSpawns ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Quarantined</span><span class=\"v\">${s.quarantined ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Graduated</span><span class=\"v good\">${s.graduated ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Rejected</span><span class=\"v bad\">${s.rejected ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Avg sliver tokens</span><span class=\"v\">${Math.round(s.avgSliverTokens ?? 0)}</span></div>\n <div class=\"row\"><span class=\"k\">Avg graduation</span><span class=\"v\">${Math.round(s.avgGraduationMs ?? 0)}ms</span></div>`;\n const q = await api('/api/v6/subagent-quarantine') ?? [];\n document.getElementById('sabb-quarantine').innerHTML = q.length ? q.map(e =>\n `<tr><td><code>${e.id.slice(0,10)}</code></td><td><span class=\"pill purple\">${e.parentAgent}</span></td><td>${e.category}</td><td>${(e.confidence||0).toFixed(2)}</td><td>${(e.content||'').slice(0,100)}</td><td style=\"text-align:right\"><button class=\"btn sm success\" onclick=\"graduate('${e.id}')\">Graduate</button> <button class=\"btn sm danger\" onclick=\"rejectMem('${e.id}')\">Reject</button></td></tr>`\n ).join('') : '<tr><td colspan=\"6\"><div class=\"empty\">No quarantined memories</div></td></tr>';\n}\nasync function computeSliver() {\n const res = await api('/api/v6/subagent-sliver', {\n parentAgent: document.getElementById('sabb-parent').value,\n framework: document.getElementById('sabb-framework').value,\n taskDescription: document.getElementById('sabb-task').value,\n tokenBudget: parseInt(document.getElementById('sabb-budget').value || '300'),\n });\n if (res) { document.getElementById('sabb-sliver-output').textContent = res.markdown; toast('Sliver computed', res.memories.length + ' memories \u00B7 ' + res.tokenCount + ' tokens'); }\n}\nasync function graduate(id) { const r = await api('/api/v6/subagent-graduate', { memoryId: id }); if (r?.ok) { toast('Graduated', id.slice(0,10)); refreshSABB(); } }\nasync function rejectMem(id) { const r = await api('/api/v6/subagent-reject', { memoryId: id, reason: 'manual-reject' }); if (r?.ok) { toast('Rejected', id.slice(0,10), 'warn'); refreshSABB(); } }\n\n// \u2500\u2500\u2500\u2500 Causal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCausal() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.causal ?? {};\n document.getElementById('causal-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total links</span><span class=\"v\">${s.totalLinks ?? 0}</span></div><div class=\"row\"><span class=\"k\">Effects</span><span class=\"v\">${s.effects ?? 0}</span></div><div class=\"row\"><span class=\"k\">Causes</span><span class=\"v\">${s.causes ?? 0}</span></div>`;\n}\nasync function traceCausal() {\n const id = document.getElementById('causal-mem-id').value;\n if (!id) return toast('Missing', 'Enter a memory ID', 'warn');\n const res = await api('/api/v6/causal-trace', { memoryId: id, maxDepth: parseInt(document.getElementById('causal-depth').value || '8') });\n if (res) renderCausalDag(res, id);\n}\nasync function influenceCausal() {\n const id = document.getElementById('causal-mem-id').value;\n if (!id) return toast('Missing', 'Enter a memory ID', 'warn');\n const res = await api('/api/v6/causal-influence', { memoryId: id, maxDepth: 6 });\n if (res) renderCausalDag(res, id);\n}\nfunction renderCausalDag(chain, rootId) {\n const wrap = document.getElementById('causal-dag-wrap');\n const cv = document.getElementById('causal-dag');\n const empty = document.getElementById('causal-dag-empty');\n if (!cv || !wrap) return;\n const nodes = (chain && chain.nodes) || [];\n if (!nodes.length) { if (empty) { empty.style.display = 'flex'; empty.textContent = 'No causal links recorded for this memory yet.'; } return; }\n if (empty) empty.style.display = 'none';\n cv.width = wrap.clientWidth || 800; cv.height = wrap.clientHeight || 420;\n const W = cv.width, H = cv.height, ctx = cv.getContext('2d');\n // Layer nodes by depth\n const byDepth = {};\n let maxDepth = 0;\n for (const n of nodes) { const d = n.depth || 0; (byDepth[d] = byDepth[d] || []).push(n); if (d > maxDepth) maxDepth = d; }\n const pos = new Map();\n const layers = maxDepth + 1;\n for (let d = 0; d <= maxDepth; d++) {\n const row = byDepth[d] || [];\n const y = layers === 1 ? H / 2 : 40 + (d / Math.max(1, maxDepth)) * (H - 80);\n row.forEach((n, i) => { pos.set(n.id, { x: (i + 1) / (row.length + 1) * W, y: y, n: n }); });\n }\n ctx.clearRect(0, 0, W, H);\n // Links\n for (const l of (chain.links || [])) {\n const a = pos.get(l.causeId), b = pos.get(l.effectId);\n if (!a || !b) continue;\n ctx.strokeStyle = 'rgba(24,255,255,' + (0.25 + 0.5 * (l.strength || 0.5)) + ')';\n ctx.lineWidth = 1 + (l.strength || 0.5) * 2;\n ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.stroke();\n // arrowhead\n const ang = Math.atan2(b.y - a.y, b.x - a.x);\n ctx.fillStyle = 'rgba(24,255,255,0.7)';\n ctx.beginPath(); ctx.moveTo(b.x - 11 * Math.cos(ang - 0.4), b.y - 11 * Math.sin(ang - 0.4)); ctx.lineTo(b.x, b.y); ctx.lineTo(b.x - 11 * Math.cos(ang + 0.4), b.y - 11 * Math.sin(ang + 0.4)); ctx.fill();\n }\n // Nodes\n for (const p of pos.values()) {\n const isRoot = p.n.id === rootId;\n const col = isRoot ? '#ec4899' : '#a855f7';\n const r = isRoot ? 13 : 9;\n const halo = ctx.createRadialGradient(p.x, p.y, r * 0.4, p.x, p.y, r * 2.4);\n halo.addColorStop(0, col + '88'); halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(p.x, p.y, r * 2.4, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(p.x, p.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = col; ctx.lineWidth = 2; ctx.stroke();\n ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';\n ctx.font = '10px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n const lbl = String(p.n.content || p.n.category || p.n.id).slice(0, 22);\n ctx.fillText(lbl, p.x, p.y + r + 10);\n }\n toast('Causal chain', nodes.length + ' nodes \u00B7 ' + ((chain.links || []).length) + ' links');\n}\nasync function linkCausal() {\n const r = await api('/api/v6/causal-link', {\n effectId: document.getElementById('causal-effect').value,\n causeId: document.getElementById('causal-cause').value,\n rationale: document.getElementById('causal-rationale').value,\n });\n if (r) { toast('Link recorded'); refreshCausal(); }\n}\n\n// \u2500\u2500\u2500\u2500 Collision \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCollision() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.collision ?? {};\n document.getElementById('collision-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Active intents</span><span class=\"v\">${s.activeIntents ?? 0}</span></div><div class=\"row\"><span class=\"k\">Collisions detected</span><span class=\"v warn\">${s.collisionsDetected ?? 0}</span></div><div class=\"row\"><span class=\"k\">Resolved</span><span class=\"v good\">${s.collisionsResolved ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg overlap</span><span class=\"v\">${(s.avgOverlapLines ?? 0).toFixed(1)} lines</span></div>`;\n const data = await api('/api/v6/collision-list') ?? { intents: [], alerts: [] };\n document.getElementById('collision-alerts').innerHTML = data.alerts.length\n ? data.alerts.map(a => `<div style=\"padding:10px;background:var(--bg-elev-2);border-left:3px solid var(--danger);border-radius:6px;margin-bottom:6px\"><div style=\"font-weight:600;font-size:12px\">${(a.filePath||'').split(/[\\\\/]/).pop()}</div><div class=\"mono\" style=\"margin-top:4px;color:var(--text-2)\">${a.suggestedResolution}</div></div>`).join('')\n : '<div class=\"empty\">No active alerts</div>';\n document.getElementById('collision-intents').innerHTML = data.intents.length ? data.intents.map(i =>\n `<tr><td><span class=\"pill cyan\">${i.agentTool}</span></td><td><code>${(i.filePath||'').split(/[\\\\/]/).pop()}</code></td><td>${i.startLine}-${i.endLine}</td><td>${(i.intent||'').slice(0,80)}</td><td class=\"mono\">${new Date(i.expiresAt).toLocaleTimeString()}</td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No active intents</div></td></tr>';\n}\n\n// \u2500\u2500\u2500\u2500 Dream \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshDream() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.dream ?? {};\n document.getElementById('dream-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total</span><span class=\"v\">${s.totalDreams ?? 0}</span></div><div class=\"row\"><span class=\"k\">Actionable</span><span class=\"v good\">${s.actionableCount ?? 0}</span></div><div class=\"row\"><span class=\"k\">Acknowledged</span><span class=\"v\">${s.acknowledgedCount ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg confidence</span><span class=\"v\">${((s.avgConfidence ?? 0) * 100).toFixed(0)}%</span></div>`;\n if (s.byType) drawDreamChart(s.byType);\n const dreams = await api('/api/v6/dream-list') ?? [];\n document.getElementById('dream-list').innerHTML = dreams.length ? dreams.map(d =>\n `<tr><td><span class=\"pill purple\">${d.type}</span></td><td>${(d.content||'').slice(0,160)}</td><td>${((d.confidence||0)*100).toFixed(0)}%</td><td>${d.actOnNextSession ? '<span class=\"pill green\">yes</span>' : '\u2014'}</td><td><button class=\"btn sm\" onclick=\"ackDream('${d.id}')\">Ack</button></td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No dreams yet. Run a cycle.</div></td></tr>';\n}\nasync function runDream() { const d = await api('/api/v6/dream-run', {}); if (d) { toast('Dreams generated', (d.length || 0) + ' new'); refreshDream(); } }\nasync function startDreamLoop() { await api('/api/v6/dream-start', {}); toast('Dream loop', 'Background reflection active'); }\nasync function ackDream(id) { await api('/api/v6/dream-ack', { id }); refreshDream(); }\n\n// \u2500\u2500\u2500\u2500 Reputation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshReputation() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const s = hive?.modules.reputation ?? {};\n document.getElementById('reputation-stats').innerHTML = `<div class=\"row\"><span class=\"k\">Total agents</span><span class=\"v\">${s.totalAgents ?? 0}</span></div><div class=\"row\"><span class=\"k\">Total receipts</span><span class=\"v\">${s.totalReceipts ?? 0}</span></div><div class=\"row\"><span class=\"k\">Verified</span><span class=\"v good\">${s.verifiedReceipts ?? 0}</span></div><div class=\"row\"><span class=\"k\">Avg accuracy</span><span class=\"v\">${((s.averageAccuracy ?? 0) * 100).toFixed(1)}%</span></div>`;\n const top = s.topAgents ?? [];\n document.getElementById('rep-leaderboard').innerHTML = top.length ? top.map(a =>\n `<tr><td><span class=\"pill cyan\">${a.agentTool}</span></td><td>${((a.accuracy ?? 0) * 100).toFixed(1)}%</td><td>${a.decisions}</td><td class=\"mono\">\u2014</td></tr>`\n ).join('') : '<tr><td colspan=\"4\"><div class=\"empty\">No signed decisions yet</div></td></tr>';\n const grid = document.getElementById('rep-trust-grid');\n if (grid) {\n if (!top.length) {\n grid.innerHTML = emptyState('shield', 'No trust receipts yet', 'Agents build a portable trust score by signing decision receipts. Run: shadow-brain reputation sign --agent <name>', null);\n } else {\n grid.innerHTML = top.map(function (a, i) {\n return '<div class=\"trust-card\"><div class=\"trust-ring\"><canvas id=\"trust-ring-' + i + '\"></canvas><div class=\"tr-pct\">' + Math.round((a.accuracy || 0) * 100) + '</div></div><div class=\"tc-name\">' + htmlEscape(a.agentTool) + '</div><div class=\"tc-sub\">' + a.decisions + ' decisions</div></div>';\n }).join('');\n top.forEach(function (a, i) { drawTrustRing('trust-ring-' + i, a.accuracy || 0); });\n }\n }\n}\nfunction drawTrustRing(id, frac) {\n const cv = document.getElementById(id); if (!cv || !cv.getContext) return;\n cv.width = 92; cv.height = 92;\n const ctx = cv.getContext('2d'), cx = 46, cy = 46, r = 38;\n ctx.clearRect(0, 0, 92, 92);\n ctx.lineWidth = 8; ctx.strokeStyle = 'rgba(120,140,180,0.18)';\n ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();\n const col = frac >= 0.8 ? '#10e68c' : frac >= 0.5 ? '#ffb53f' : '#ff5864';\n ctx.strokeStyle = col; ctx.lineCap = 'round';\n ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + frac * Math.PI * 2); ctx.stroke();\n}\nasync function genBadge() {\n const res = await api('/api/v6/reputation-badge', { agentTool: document.getElementById('rep-agent').value, agentVersion: document.getElementById('rep-ver').value || undefined });\n document.getElementById('rep-badge-output').textContent = res?.badge || 'No reputation data yet. Sign a decision first via CLI: shadow-brain reputation sign --agent ... --ver ...';\n}\n\n// \u2500\u2500\u2500\u2500 Debate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runDebate() {\n const res = await api('/api/v6/debate-run', {\n question: document.getElementById('debate-question').value,\n context: document.getElementById('debate-context').value,\n turns: parseInt(document.getElementById('debate-turns').value || '2'),\n });\n if (res) {\n document.getElementById('debate-output').textContent = res.turns.map(t => '[' + t.position + '] ' + t.statement).join('\\n\\n') + '\\n\\nVERDICT: ' + res.verdict;\n toast('Debate complete', res.durationMs + 'ms');\n }\n}\n\n// \u2500\u2500\u2500\u2500 Pre-mortem \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runPremortem() {\n const task = document.getElementById('pm-task').value;\n if (!task.trim()) return toast('Missing', 'Describe the task first', 'warn');\n const res = await api('/api/v6/premortem-run', { taskDescription: task, projectDir: '' });\n if (!res) return;\n const out = document.getElementById('pm-output');\n const sevColor = { critical: '#ff5864', high: '#ffb53f', medium: '#ffca28', low: '#10e68c' };\n const riskCol = res.riskScore > 0.6 ? '#ff5864' : res.riskScore > 0.3 ? '#ffb53f' : '#10e68c';\n const failures = res.failures || [];\n // Risk matrix buckets: rows = severity (crit..low), cols = probability quartile\n const sevOrder = ['critical', 'high', 'medium', 'low'];\n const cells = {};\n for (const f of failures) {\n const col = Math.min(3, Math.floor((f.probability || 0) * 4));\n const key = (f.severity || 'low') + ':' + col;\n cells[key] = (cells[key] || 0) + 1;\n }\n const cellColor = (sev, col) => {\n const sevW = { critical: 3, high: 2, medium: 1, low: 0 }[sev];\n const risk = (sevW + col) / 6;\n return risk > 0.6 ? 'rgba(255,88,100,0.85)' : risk > 0.35 ? 'rgba(255,181,63,0.8)' : 'rgba(16,230,140,0.7)';\n };\n let matrix = '<div class=\"risk-matrix\"><div class=\"rm-h\"></div><div class=\"rm-h\">P 0-25%</div><div class=\"rm-h\">25-50%</div><div class=\"rm-h\">50-75%</div><div class=\"rm-h\">75-100%</div>';\n for (const sev of sevOrder) {\n matrix += '<div class=\"rm-yl\">' + sev + '</div>';\n for (let col = 0; col < 4; col++) {\n const c = cells[sev + ':' + col] || 0;\n matrix += c ? '<div class=\"rm-cell\" style=\"background:' + cellColor(sev, col) + '\">' + c + '</div>' : '<div class=\"rm-cell empty\">\u00B7</div>';\n }\n }\n matrix += '</div>';\n let html = '<div class=\"risk-head\"><div class=\"risk-gauge\"><canvas id=\"pm-gauge\"></canvas><div class=\"rg-num\" style=\"color:' + riskCol + '\">' + Math.round(res.riskScore * 100) + '</div></div>';\n html += '<div><div style=\"font-size:13px;font-weight:700;margin-bottom:4px\">Overall Risk Score</div><div style=\"font-size:12px;color:var(--text-2);max-width:520px;line-height:1.5\">' + htmlEscape(res.summary || '') + '</div></div></div>';\n html += matrix;\n html += failures.map(function (f) {\n const col = sevColor[f.severity] || '#10e68c';\n return '<div class=\"fail-card\" style=\"border-left-color:' + col + '\"><div class=\"fc-head\"><span class=\"pill\" style=\"color:' + col + ';border-color:' + col + '\">' + htmlEscape(f.severity) + '</span><span class=\"pill\">p=' + (f.probability || 0).toFixed(2) + '</span><span class=\"pill\">' + htmlEscape(f.source || '') + '</span></div><div class=\"fc-desc\">' + htmlEscape(f.description || '') + '</div>' + (f.mitigation ? '<div class=\"fc-mit\">' + htmlEscape(f.mitigation) + '</div>' : '') + '</div>';\n }).join('') || emptyState('check', 'No predicted failures', 'The pre-mortem surfaced no significant risks for this task based on past incidents and similar projects.', null);\n out.innerHTML = html;\n drawRiskGauge('pm-gauge', res.riskScore, riskCol);\n}\nfunction drawRiskGauge(id, frac, col) {\n const cv = document.getElementById(id); if (!cv || !cv.getContext) return;\n cv.width = 96; cv.height = 96;\n const ctx = cv.getContext('2d'), cx = 48, cy = 48, r = 38;\n ctx.clearRect(0, 0, 96, 96);\n ctx.lineWidth = 9; ctx.strokeStyle = 'rgba(120,140,180,0.18)';\n ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.stroke();\n ctx.strokeStyle = col; ctx.lineCap = 'round';\n ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.max(0.02, frac) * Math.PI * 2); ctx.stroke();\n}\n\n// \u2500\u2500\u2500\u2500 Attention \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function runAttention() {\n const ids = document.getElementById('att-memories').value.split(',').map(s => s.trim()).filter(Boolean);\n const res = await api('/api/v6/attention-heatmap', { decisionText: document.getElementById('att-decision').value, candidateMemoryIds: ids });\n const out = document.getElementById('att-output');\n if (!res) return;\n const weights = res.weights || [];\n if (!weights.length) { out.innerHTML = emptyState('target', 'No attention weights', 'Provide a decision and some candidate memory IDs (or let it use recent memories) to see which ones carried the most weight.', null); return; }\n out.innerHTML = '<div style=\"font-size:11.5px;color:var(--text-3);margin-bottom:10px\">' + res.totalMemoriesConsidered + ' memories considered for: <strong style=\"color:var(--text-2)\">' + htmlEscape(res.decisionText || '') + '</strong></div>' + weights.map(function (w) {\n const pct = (w.weight || 0) * 100;\n const col = pct > 60 ? '#ff5864' : pct > 30 ? '#ffb53f' : '#18ffff';\n return '<div class=\"heat-row\"><div class=\"hr-top\"><span>' + htmlEscape((w.memoryContent || '').slice(0, 80)) + '</span><span class=\"hr-pct\" style=\"color:' + col + '\">' + pct.toFixed(1) + '%</span></div><div class=\"hr-track\"><div class=\"hr-fill\" style=\"width:' + Math.max(2, pct) + '%;background:' + col + '\"></div></div>' + (w.reasoning ? '<div class=\"hr-reason\">' + htmlEscape(w.reasoning) + '</div>' : '') + '</div>';\n }).join('');\n}\n\n// \u2500\u2500\u2500\u2500 Tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshTokens() {\n const res = await api('/api/v6/tokens-report');\n if (!res) return;\n const runtime = res.runtime || {};\n document.getElementById('tok-agent').textContent = runtime.currentAgentDisplay || 'Unknown';\n document.getElementById('tok-runtime').textContent = (runtime.provider || 'provider') + (runtime.model ? ' / ' + runtime.model : '');\n document.getElementById('tok-runtime-note').innerHTML = `<strong>Accuracy</strong><span>${htmlEscape(runtime.note || 'Token records are historical. Current agent and model spend are shown separately.')}</span>`;\n document.getElementById('tok-total').textContent = '$' + (res.totalSpendUsd || 0).toFixed(4);\n document.getElementById('tok-recent').textContent = '$' + (res.recentSpendUsd || 0).toFixed(4);\n document.getElementById('tok-proj').textContent = '$' + (res.monthlyProjectionUsd || 0).toFixed(2);\n document.getElementById('tok-save').textContent = '$' + (res.savingsOpportunitiesUsd || 0).toFixed(2);\n const calls = Object.values(res.byAgent || {}).reduce((a, b) => a + b.calls, 0);\n document.getElementById('tok-calls').textContent = calls;\n document.getElementById('tok-calls-sub').textContent = `${(res.totalInputTokens || 0).toLocaleString()} in \u00B7 ${(res.totalOutputTokens || 0).toLocaleString()} out`;\n drawTokensAgentChart(res.recentByAgent || res.byAgent || {});\n drawTokensModelChart(res.byModel || {});\n document.getElementById('tok-agent-table').innerHTML = renderSpendRows(res.recentByAgent || res.byAgent || {}, 'No recent agent spend records.');\n document.getElementById('tok-model-table').innerHTML = renderSpendRows(res.byModel || {}, 'No model spend records.');\n document.getElementById('tok-suggestions').innerHTML = (res.suggestions || []).map(s => `<div style=\"padding:8px 0;border-bottom:1px solid var(--border);display:flex;gap:8px\"><span style=\"color:var(--success);flex-shrink:0\">\uD83D\uDCA1</span><span style=\"font-size:12px\">${s}</span></div>`).join('') || '<div class=\"empty\">Running lean \u2014 no suggestions</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Forget \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshForget() {\n drawForgetChart([]);\n}\nasync function runForget() {\n const r = await api('/api/v6/forget-consolidate', {});\n if (!r) return;\n document.getElementById('forget-output').innerHTML = `\n <div class=\"row\"><span class=\"k\">Cycle</span><span class=\"v\">${r.cycle}</span></div>\n <div class=\"row\"><span class=\"k\">Promoted</span><span class=\"v good\">${r.promoted}</span></div>\n <div class=\"row\"><span class=\"k\">Demoted</span><span class=\"v\">${r.demoted}</span></div>\n <div class=\"row\"><span class=\"k\">Forgotten</span><span class=\"v bad\">${r.forgotten}</span></div>\n <div class=\"row\"><span class=\"k\">Strengthened</span><span class=\"v good\">${r.strengthened}</span></div>\n <div class=\"row\"><span class=\"k\">Duration</span><span class=\"v\">${r.durationMs}ms</span></div>`;\n toast('Consolidation cycle', 'cycle #' + r.cycle);\n}\n\n// \u2500\u2500\u2500\u2500 Formal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function genFormal() {\n const res = await api('/api/v6/formal-generate', { text: document.getElementById('formal-text').value });\n if (res) { document.getElementById('formal-output').textContent = JSON.stringify(res, null, 2); toast('Rule generated'); }\n}\nasync function exportEslint() {\n const res = await fetch('/api/v6/formal-export-eslint').then(r => r.text());\n document.getElementById('formal-export').textContent = res;\n}\nasync function exportSemgrep() {\n const res = await fetch('/api/v6/formal-export-semgrep').then(r => r.text());\n document.getElementById('formal-export').textContent = res;\n}\n\n// \u2500\u2500\u2500\u2500 Calibration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshCalibration() {\n const scores = await api('/api/v6/calibration-scores') ?? [];\n document.getElementById('calibration-table').innerHTML = scores.length ? scores.map(s =>\n `<tr><td><span class=\"pill cyan\">${s.agentTool}</span></td><td>${s.category}</td><td>${s.brierScore}</td><td>${s.calibrationError}</td><td>${(s.overconfidenceRatio * 100).toFixed(0)}%</td><td class=\"v ${s.trustWeight > 0.8 ? 'good' : s.trustWeight > 0.5 ? '' : 'warn'}\">${s.trustWeight}</td><td>${s.sampleSize}</td></tr>`\n ).join('') : '<tr><td colspan=\"7\"><div class=\"empty\">No calibration data yet</div></td></tr>';\n drawCalibrationChart(scores);\n}\nfunction drawCalibrationChart(scores) {\n const cv = document.getElementById('chart-calibration'); if (!cv || !window.Chart) return;\n const t = chartTheme();\n if (charts.calibration) charts.calibration.destroy();\n const rows = (scores || []).slice(0, 8);\n const labels = rows.map(function (s) { return s.agentTool + '/' + String(s.category).slice(0, 10); });\n charts.calibration = new Chart(cv, {\n type: 'bar',\n data: { labels: labels, datasets: [\n { label: 'Brier (lower better)', data: rows.map(function (s) { return s.brierScore; }), backgroundColor: '#ff5864', borderRadius: 4 },\n { label: 'Trust weight', data: rows.map(function (s) { return s.trustWeight; }), backgroundColor: '#10e68c', borderRadius: 4 },\n ] },\n options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: t.text } } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { beginAtZero: true, max: 1, grid: { color: t.grid }, ticks: { color: t.text } } } },\n });\n}\n\n// \u2500\u2500\u2500\u2500 Branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshBranch() {\n const r = await api('/api/v6/branch-state');\n const cards = document.getElementById('branch-cards');\n const cats = document.getElementById('branch-cats');\n if (!r) { if (cards) cards.innerHTML = emptyState('git-branch', 'No branch data', 'Branch-aware memory activates inside a git repository. Open a project with branches to see scoped memory here.', null); return; }\n if (cards) cards.innerHTML =\n '<div class=\"card stat-card accent\"><div class=\"label\">Current Branch</div><div class=\"value\" style=\"font-size:20px\" id=\"br-cur\">' + htmlEscape(r.currentBranch || '\u2014') + '</div><div class=\"trend\">active context</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Branch Memories</div><div class=\"value\">' + (r.branchMemoryCount ?? 0) + '</div><div class=\"trend\">scoped to branch</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Global Memories</div><div class=\"value\">' + (r.globalMemoryCount ?? 0) + '</div><div class=\"trend\">all branches</div></div>' +\n '<div class=\"card stat-card accent\"><div class=\"label\">Active IDs</div><div class=\"value\">' + ((r.activeMemoryIds || []).length) + '</div><div class=\"trend\">in working set</div></div>';\n if (cats) {\n const list = r.branchSpecificCategories || [];\n cats.innerHTML = list.length ? list.map(function (c) { return '<span class=\"tc\">' + htmlEscape(String(c)) + '</span>'; }).join('') : '<div class=\"empty-tight\">No branch-specific categories.</div>';\n }\n}\n\n// \u2500\u2500\u2500\u2500 Privacy \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshPrivacy() {\n const hive = state.hive || await api('/api/v6/hive-status');\n const a = hive?.modules.airGap ?? {};\n document.getElementById('airgap-state').innerHTML = `\n <div class=\"row\"><span class=\"k\">Enabled</span><span class=\"v ${a.enabled ? 'good' : ''}\">${a.enabled ? 'YES' : 'no'}</span></div>\n <div class=\"row\"><span class=\"k\">Policy</span><span class=\"v\">${a.policy || '\u2014'}</span></div>\n <div class=\"row\"><span class=\"k\">Blocked outbound</span><span class=\"v bad\">${a.blockedOutboundCount ?? 0}</span></div>\n <div class=\"row\"><span class=\"k\">Allowed local</span><span class=\"v good\">${a.allowedLocalCount ?? 0}</span></div>`;\n const qlist = await api('/api/v6/quarantine-list?pendingOnly=true') ?? [];\n document.getElementById('quarantine-state').innerHTML = `<div class=\"row\"><span class=\"k\">Pending</span><span class=\"v\">${qlist.length}</span></div><div class=\"row\"><span class=\"k\">Auto-delete after</span><span class=\"v\">7 days</span></div>`;\n document.getElementById('quarantine-list').innerHTML = qlist.length ? qlist.map(e =>\n `<tr><td><code>${e.id.slice(0,10)}</code></td><td><span class=\"pill\">${e.source}</span></td><td>${(e.claim||'').slice(0,100)}</td><td class=\"mono\">${e.reasonFlagged}</td><td style=\"text-align:right\"><button class=\"btn sm success\">Promote</button> <button class=\"btn sm danger\">Reject</button></td></tr>`\n ).join('') : '<tr><td colspan=\"5\"><div class=\"empty\">No quarantined claims</div></td></tr>';\n}\nasync function enableAirGap() { await api('/api/v6/airgap-enable', { policy: 'strict' }); toast('Air-gap enabled','strict mode'); refresh(); refreshPrivacy(); }\nasync function disableAirGap() { await api('/api/v6/airgap-disable', {}); toast('Air-gap disabled','', 'warn'); refresh(); refreshPrivacy(); }\nasync function toggleAirGap() { const hive = state.hive || await api('/api/v6/hive-status'); if (hive?.modules.airGap?.enabled) disableAirGap(); else enableAirGap(); }\n\n// \u2500\u2500\u2500\u2500 Voice \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function sendVoice() {\n const r = await api('/api/v6/voice-process', { transcript: document.getElementById('voice-input').value });\n if (r) document.getElementById('voice-output').textContent = `Intent: ${r.intent}\\n\\n${r.response}`;\n}\n\n// \u2500\u2500\u2500\u2500 Garden \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshGarden() {\n const snap = await api('/api/v6/garden-snapshot?limit=200') ?? [];\n const stats = await api('/api/v6/garden-stats') ?? { nodes: 0, avgBloom: 0, linkedFraction: 0 };\n document.getElementById('garden-stats').textContent = `Nodes: ${stats.nodes} \u00B7 avg bloom: ${stats.avgBloom} \u00B7 linked: ${Math.round((stats.linkedFraction||0) * 100)}%`;\n drawGarden(snap);\n}\nfunction drawGarden(nodes) {\n const c = document.getElementById('garden-canvas');\n if (!c) return;\n const ctx = c.getContext('2d'); const W = c.width, H = c.height;\n ctx.clearRect(0, 0, W, H);\n if (!nodes.length) { ctx.fillStyle = '#556080'; ctx.font = '14px \"JetBrains Mono\", monospace'; ctx.textAlign = 'center'; ctx.fillText('No garden yet. Let the brain grow.', W/2, H/2); return; }\n const pos = nodes.map((n, i) => {\n const a = i / nodes.length * Math.PI * 2;\n const r = 110 + (1 - n.bloom) * 170 + Math.sin(i * 1.3) * 30;\n return { x: W/2 + Math.cos(a) * r, y: H/2 + Math.sin(a) * r * 0.75, node: n };\n });\n ctx.strokeStyle = 'rgba(24,255,255,0.1)'; ctx.lineWidth = 1;\n for (const p of pos) for (const c of p.node.connections) { const t = pos.find(x => x.node.id === c); if (t) { ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(t.x, t.y); ctx.stroke(); } }\n for (const p of pos) {\n const col = p.node.kind === 'decision' ? '#a855f7' : p.node.kind === 'pattern' ? '#18ffff' : '#10e68c';\n const rad = 4 + p.node.bloom * 9;\n const grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, rad * 2.5);\n grd.addColorStop(0, col + 'cc'); grd.addColorStop(1, col + '00');\n ctx.fillStyle = grd; ctx.beginPath(); ctx.arc(p.x, p.y, rad * 2.5, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = col; ctx.globalAlpha = Math.max(0.4, p.node.bloom);\n ctx.beginPath(); ctx.arc(p.x, p.y, rad, 0, Math.PI * 2); ctx.fill();\n ctx.globalAlpha = 1;\n }\n}\n\n// \u2500\u2500\u2500\u2500 PR Review \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function genPRReview() {\n const r = await api('/api/v6/pr-review-generate', {\n repo: document.getElementById('pr-repo').value,\n prNumber: parseInt(document.getElementById('pr-num').value || '0'),\n changedFiles: document.getElementById('pr-files').value.split(',').map(s => s.trim()).filter(Boolean),\n diffSummary: document.getElementById('pr-diff').value,\n });\n if (r) { document.getElementById('pr-output').textContent = r.body; toast('Review generated'); }\n}\n\n// \u2500\u2500\u2500\u2500 Team \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshTeam() {\n const self = await api('/api/v6/team-self') ?? {};\n const peers = await api('/api/v6/team-peers') ?? [];\n document.getElementById('team-self').innerHTML = `<div class=\"row\"><span class=\"k\">Peer ID</span><span class=\"v mono\">${self.peerId || '\u2014'}</span></div><div class=\"row\"><span class=\"k\">Display name</span><span class=\"v\">${self.displayName || '\u2014'}</span></div>`;\n document.getElementById('team-peers').innerHTML = peers.length ? peers.map(p => `<div class=\"row\"><span class=\"k\">${p.displayName}</span><span class=\"v mono\">${p.peerId}</span></div>`).join('') : '<div class=\"empty\">No peers connected. Connect via WebRTC.</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Exchange \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshExchange() {\n const packages = await api('/api/v6/exchange-list') ?? [];\n document.getElementById('ex-list').innerHTML = packages.length ? packages.map(p => `<div class=\"row\"><span class=\"k\"><code>${p.name}</code><br/><span style=\"font-size:10px;color:var(--text-3)\">by ${p.author}</span></span><span class=\"v\">${p.memoryCount} memories</span></div>`).join('') : '<div class=\"empty\">No local packages yet</div>';\n}\nasync function exportPackage() {\n const res = await api('/api/v6/exchange-export', {\n name: document.getElementById('ex-name').value || 'untitled',\n description: document.getElementById('ex-desc').value,\n author: document.getElementById('ex-author').value,\n tags: document.getElementById('ex-tags').value.split(',').map(s => s.trim()).filter(Boolean),\n });\n if (res) { document.getElementById('ex-out').textContent = 'Saved \u2192 ' + res.filePath; toast('Package exported'); refreshExchange(); }\n}\n\n// \u2500\u2500\u2500\u2500 Features \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshFeatures() {\n document.getElementById('feature-grid').innerHTML = '<div><p>Runtime controls have moved to the workspace. Each switch describes the behavior it changes.</p><p style=\"margin-top:16px\"><a class=\"btn primary\" href=\"/#features\">Open feature controls</a></p></div>';\n}\n\n// \u2500\u2500\u2500\u2500 Models & Intelligence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet _providers = [];\nlet _allModels = {};\nasync function refreshModels() {\n const data = await api('/api/v6/providers');\n if (!data) return;\n _providers = data.providers ?? [];\n\n // Discovered agents section\n document.getElementById('discovered-agents').innerHTML = data.discovered.length\n ? data.discovered.map(d => `<div class=\"row\"><span class=\"k\"><span class=\"pill purple\">${d.agent}</span> \u2192 <span class=\"pill cyan\">${d.provider}</span> <span class=\"tag-code\">${d.model || '(none)'}</span></span><span class=\"v mono\" style=\"color:var(--text-3)\" title=\"${d.sourcePath}\">${d.apiKey ? '\uD83D\uDD11 API key available' : 'no API key found'}</span></div>`).join('')\n : '<div class=\"empty\">No agent configs found yet. Install Claude Code / Cursor / Cline / Codex / Aider / Kilo / Roo / OpenCode, or add a custom path below.</div>';\n\n // Provider cards\n document.getElementById('providers-grid').innerHTML = _providers.map(p => {\n const okClass = p.apiKey ? 'good' : p.id === 'ollama' ? '' : 'warn';\n return `<div class=\"card\" style=\"padding:14px\">\n <div style=\"display:flex;align-items:center;justify-content:space-between;margin-bottom:8px\"><div style=\"font-weight:700;font-size:13px\">${p.displayName}</div><span class=\"pill ${p.enabled ? 'green' : ''}\">${p.enabled ? 'enabled' : 'off'}</span></div>\n <div class=\"field\" style=\"margin-bottom:6px\"><label>API Key ${p.source === 'agent-discovered' ? '(from ' + (p.discoveredFrom || 'agent') + ')' : p.source === 'env' ? '(env)' : ''}</label><input type=\"password\" id=\"pkey-${p.id}\" placeholder=\"${p.apiKey ? '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022' : 'sk-...'}\" value=\"${p.apiKey || ''}\"/></div>\n <div class=\"field\" style=\"margin-bottom:6px\"><label>Default Model</label><input id=\"pmodel-${p.id}\" value=\"${p.defaultModel || ''}\"/></div>\n <div class=\"flex gap\">\n <button class=\"btn sm primary\" onclick=\"saveProvider('${p.id}')\">Save</button>\n <button class=\"btn sm\" onclick=\"testProvider('${p.id}')\">Test</button>\n <button class=\"btn sm\" onclick=\"fetchProviderModels('${p.id}')\">Fetch Models</button>\n </div>\n <div class=\"mono mt\" id=\"pmodels-${p.id}\" style=\"max-height:120px;overflow-y:auto;font-size:10.5px;color:var(--text-3)\"></div>\n </div>`;\n }).join('');\n\n // Intelligence config\n const intel = await api('/api/v6/intelligence');\n if (intel) {\n const provSel = document.getElementById('intel-provider');\n provSel.innerHTML = _providers.map(p => `<option value=\"${p.id}\" ${intel.leadProvider === p.id ? 'selected' : ''}>${p.displayName}</option>`).join('');\n // Populate model field\n const modelInput = document.getElementById('intel-model');\n modelInput.innerHTML = `<option value=\"${intel.leadModel}\">${intel.leadModel}</option>`;\n document.getElementById('intel-agent-proxy').checked = intel.useAgentToolModels;\n document.getElementById('intel-local-first').checked = intel.preferLocalFirst;\n }\n\n // Custom agents\n const ca = await api('/api/v6/custom-agents');\n const agents = ca?.agents ?? [];\n document.getElementById('custom-agents-list').innerHTML = agents.length\n ? agents.map((a, i) => `<div class=\"row\"><span class=\"k\"><span class=\"pill purple\">${a.name}</span> <span class=\"tag-code\">${a.path}</span></span><span class=\"v\"><button class=\"btn sm danger\" onclick=\"removeCustomAgent(${i})\">Remove</button></span></div>`).join('')\n : '<div class=\"empty\">No custom agent paths yet</div>';\n}\nasync function saveProvider(id) {\n const apiKey = document.getElementById('pkey-' + id).value;\n const defaultModel = document.getElementById('pmodel-' + id).value;\n const r = await api('/api/v6/provider-save', { provider: id, apiKey: apiKey || undefined, defaultModel, enabled: true });\n if (r) { toast('Saved', id); refreshModels(); }\n}\nasync function testProvider(id) {\n const r = await api('/api/v6/provider-test', { provider: id });\n toast(r?.ok ? 'Connected' : 'Failed', r?.ok ? (r.modelCount + ' models available') : (r?.error || 'Unknown error'), r?.ok ? 'success' : 'error');\n}\nasync function fetchProviderModels(id) {\n const models = await api('/api/v6/provider-models', { provider: id });\n if (!models) return;\n _allModels[id] = models;\n document.getElementById('pmodels-' + id).innerHTML = models.length\n ? models.slice(0, 50).map(m => `<div style=\"padding:3px 0\"><span class=\"tag-code\">${m.id}</span>${m.tags.length ? ' <span style=\"color:var(--text-dim)\">' + m.tags.join(' \u00B7 ') + '</span>' : ''}</div>`).join('')\n : '<div>No models (check API key + network)</div>';\n toast('Fetched', models.length + ' models from ' + id);\n}\nasync function saveIntelligence() {\n const provId = document.getElementById('intel-provider').value;\n const model = document.getElementById('intel-model').value;\n const useAgent = document.getElementById('intel-agent-proxy').checked;\n const local = document.getElementById('intel-local-first').checked;\n const r = await api('/api/v6/intelligence', {\n leadProvider: provId, leadModel: model,\n useAgentToolModels: useAgent, preferLocalFirst: local,\n });\n document.getElementById('intel-result').textContent = r ? '\u2713 Intelligence config saved \u00B7 lead: ' + provId + ' / ' + model : '\u2717 Save failed';\n if (r) toast('Intelligence updated');\n}\nasync function addCustomAgent() {\n const name = document.getElementById('ca-name').value.trim();\n const p = document.getElementById('ca-path').value.trim();\n if (!name || !p) return toast('Missing', 'Name + path required', 'warn');\n const cur = await api('/api/v6/custom-agents');\n const agents = [...(cur?.agents || []), { name, path: p }];\n await api('/api/v6/custom-agents', { agents });\n document.getElementById('ca-name').value = ''; document.getElementById('ca-path').value = '';\n toast('Added', name); refreshModels();\n}\nasync function removeCustomAgent(i) {\n const cur = await api('/api/v6/custom-agents');\n const agents = (cur?.agents || []).filter((_, idx) => idx !== i);\n await api('/api/v6/custom-agents', { agents });\n toast('Removed', '', 'warn'); refreshModels();\n}\n\n// \u2500\u2500\u2500\u2500 Chat with Brain \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet currentConversationId = null;\nasync function refreshChat() {\n const list = await api('/api/v6/chat-conversations');\n const el = document.getElementById('chat-conversations');\n el.innerHTML = (list || []).map(c => `<div class=\"row\" onclick=\"loadConv('${c.id}')\" style=\"cursor:pointer\"><span class=\"k\" style=\"min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">${c.preview || '(new)'}</span><span class=\"v mono\" style=\"font-size:10px\">${c.turnCount}</span></div>`).join('') || '<div class=\"empty\">No chats yet</div>';\n if (!currentConversationId) renderChatMessages([]);\n}\nasync function loadConv(id) { currentConversationId = id; const turns = await api('/api/v6/chat-conversation', { conversationId: id }) ?? []; renderChatMessages(turns); }\nfunction newChat() { currentConversationId = null; renderChatMessages([]); document.getElementById('chat-input').focus(); }\nasync function clearAllChats() { if (!confirm('Clear ALL chat history?')) return; await api('/api/v6/chat-clear-all', {}); currentConversationId = null; refreshChat(); renderChatMessages([]); toast('Chat history cleared','','warn'); }\nfunction renderChatMessages(turns) {\n const el = document.getElementById('chat-messages');\n if (!turns.length) { el.innerHTML = '<div class=\"empty\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/></svg>Ask your brain anything.<br/>It will search memories + cite them.</div>'; return; }\n el.innerHTML = turns.map(t => {\n const isUser = t.role === 'user';\n const cites = (t.citations || []).length ? `<div style=\"margin-top:8px;padding:8px;background:var(--bg-elev-1);border-radius:6px\"><div style=\"font-size:10px;color:var(--text-3);margin-bottom:4px;text-transform:uppercase;letter-spacing:0.04em\">Citations (${t.citations.length})</div>${t.citations.map((c, i) => `<div style=\"padding:4px 0;border-bottom:1px solid var(--border);font-size:11px\"><span class=\"pill cyan\">[${i+1}]</span> <span class=\"pill\">${c.agent}/${c.category}</span> ${c.content.slice(0, 180)} <span style=\"color:var(--text-dim)\">(${(c.score*100).toFixed(0)}%)</span></div>`).join('')}</div>` : '';\n const meta = !isUser && t.provider ? `<div class=\"mono\" style=\"font-size:10px;color:var(--text-3);margin-top:6px\">${t.provider}/${t.model || ''} \u00B7 ${t.tokensUsed || 0} tok${t.cached ? ' \u00B7 cached' : ''}</div>` : '';\n return `<div style=\"display:flex;gap:10px;margin-bottom:14px;${isUser ? '' : 'background:var(--bg-elev-1);padding:12px;border-radius:10px'}\"><div style=\"width:28px;height:28px;border-radius:50%;background:${isUser ? 'var(--brand-gradient)' : 'var(--bg-elev-3)'};display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:11px;font-weight:700\">${isUser ? 'You' : '\uD83E\uDDE0'}</div><div style=\"flex:1;min-width:0\"><div style=\"white-space:pre-wrap;font-size:13px;line-height:1.5\">${(t.content || '').replace(/\\</g, '<')}</div>${cites}${meta}</div></div>`;\n }).join('');\n el.scrollTop = el.scrollHeight;\n}\nasync function sendChat() {\n const input = document.getElementById('chat-input');\n const question = input.value.trim();\n if (!question) return;\n input.value = '';\n const btn = document.getElementById('chat-send-btn');\n btn.disabled = true;\n document.getElementById('chat-status').textContent = 'Searching memories + calling LLM...';\n const turns = currentConversationId ? (await api('/api/v6/chat-conversation', { conversationId: currentConversationId }) ?? []) : [];\n turns.push({ role: 'user', content: question, createdAt: new Date() });\n renderChatMessages(turns);\n try {\n const res = await api('/api/v6/chat', { question, conversationId: currentConversationId, maxCitations: 6 });\n if (res) {\n currentConversationId = res.conversationId;\n const updated = await api('/api/v6/chat-conversation', { conversationId: currentConversationId }) ?? [];\n renderChatMessages(updated);\n refreshChat();\n document.getElementById('chat-status').textContent = 'Answered in ' + (res.ms || '?') + 'ms \u00B7 ' + (res.citations?.length || 0) + ' citations';\n }\n } finally {\n btn.disabled = false;\n }\n}\n\n// \u2500\u2500\u2500\u2500 Stop button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function stopBrain() {\n if (!confirm('Stop Shadow Brain?\\n\\nThe npm process will exit. You will need to restart it from your terminal.')) return;\n try {\n await fetch('/api/v6/shutdown', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });\n } catch { /* server is shutting down */ }\n toast('Shutting down', 'Shadow Brain is stopping...', 'warn');\n setTimeout(() => { document.body.style.filter = 'grayscale(1) brightness(0.4)'; document.body.innerHTML = '<div style=\"display:flex;align-items:center;justify-content:center;height:100vh;color:#888;font-family:ui-monospace,monospace;font-size:16px;flex-direction:column;gap:16px\"><div>\u23FB Shadow Brain stopped</div><div style=\"font-size:12px\">Restart with: <code style=\"background:#222;padding:4px 8px;border-radius:4px\">npm run dev</code> or <code style=\"background:#222;padding:4px 8px;border-radius:4px\">shadow-brain dash .</code></div></div>'; }, 1500);\n}\n\n// When the intel provider changes, re-fetch its models to populate the model dropdown\ndocument.addEventListener('change', async e => {\n if (e.target && e.target.id === 'intel-provider') {\n const id = e.target.value;\n const models = _allModels[id] || await api('/api/v6/provider-models', { provider: id });\n _allModels[id] = models || [];\n const sel = document.getElementById('intel-model');\n if (sel) sel.innerHTML = (models || []).map(m => `<option value=\"${m.id}\">${m.displayName}</option>`).join('') || '<option>no models available</option>';\n }\n});\n\n// \u2500\u2500\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshConfig() {\n const hive = state.hive || await api('/api/v6/hive-status');\n if (!hive) return;\n document.getElementById('config-state').innerHTML = `\n <div class=\"row\"><span class=\"k\">Version</span><span class=\"v\">${hive.version}</span></div>\n <div class=\"row\"><span class=\"k\">Local-first</span><span class=\"v ${hive.localFirst ? 'good' : 'warn'}\">${hive.localFirst ? 'YES' : 'no'}</span></div>\n <div class=\"row\"><span class=\"k\">Agents connected</span><span class=\"v\">${hive.totalAgentsConnected}</span></div>\n <div class=\"row\"><span class=\"k\">Memories stored</span><span class=\"v\">${hive.totalMemoriesStored}</span></div>\n <div class=\"row\"><span class=\"k\">Modules active</span><span class=\"v good\">${Object.keys(hive.modules || {}).length}</span></div>`;\n}\nasync function saveConfig() { const r = await api('/api/v6/config-save', { provider: document.getElementById('cfg-provider').value, apiKey: document.getElementById('cfg-apikey').value || undefined, model: document.getElementById('cfg-model').value || undefined }); document.getElementById('cfg-result').textContent = r?.ok ? '\u2713 Saved' : '\u2717 Failed'; if (r?.ok) toast('Config saved'); }\nasync function testConnection() { const r = await api('/api/v6/config-test', { provider: document.getElementById('cfg-provider').value }); document.getElementById('cfg-result').textContent = r?.ok ? '\u2713 Connected' : '\u2717 ' + (r?.error || 'Failed'); toast(r?.ok ? 'Connection OK' : 'Connection failed', r?.error || '', r?.ok ? 'success' : 'error'); }\nasync function startMCP() { const r = await api('/api/v6/mcp-start', { port: parseInt(document.getElementById('mcp-port').value || '7342'), authToken: document.getElementById('mcp-token').value || undefined }); document.getElementById('mcp-result').textContent = r?.ok ? '\u2713 Started at :' + (r.port ?? 7342) : '\u2717 ' + (r?.error || 'Failed'); toast(r?.ok ? 'MCP started' : 'MCP failed', r?.error, r?.ok ? 'success' : 'error'); }\nasync function stopMCP() { const r = await api('/api/v6/mcp-stop', {}); document.getElementById('mcp-result').textContent = r?.ok ? '\u2713 Stopped' : '\u2717 ' + (r?.error || 'Failed'); toast('MCP stopped', '', 'warn'); }\n\n// \u2500\u2500\u2500\u2500 Agents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshAgents() {\n const list = await api('/api/v6/agents-list', {}) ?? [];\n document.getElementById('badge-agents').textContent = list.filter(a => a.hookInstalled).length;\n document.getElementById('agents-table').innerHTML = list.map(a => `\n <tr>\n <td><strong>${a.displayName}</strong><br/><code style=\"color:var(--text-3);font-size:10px\">${a.name}</code></td>\n <td>${a.detected ? '<span class=\"pill green\">detected</span>' : '<span class=\"pill\">not found</span>'}</td>\n <td>${a.hookInstalled ? '<span class=\"pill cyan\">active</span>' : '<span class=\"pill\">not wired</span>'}</td>\n <td class=\"mono\">${a.hookPath ? a.hookPath.slice(0, 70) : '\u2014'}</td>\n <td style=\"text-align:right\">${a.hookInstalled ? `<button class=\"btn sm danger\" onclick=\"detachOne('${a.name}')\">Disconnect</button>` : `<button class=\"btn sm success\" onclick=\"attachOne('${a.name}')\">Connect</button>`}</td>\n </tr>`).join('') || '<tr><td colspan=\"5\"><div class=\"empty\">No agents detected</div></td></tr>';\n}\nasync function attachOne(agent) { await api('/api/v6/agents-attach', { agent }); toast('Attached', agent); refreshAgents(); }\nasync function detachOne(agent) { await api('/api/v6/agents-detach', { agent }); toast('Detached', agent, 'warn'); refreshAgents(); }\nasync function attachAll() { const r = await api('/api/v6/agents-attach-all', {}); if (r) { toast('Attach All', (r.attached?.length ?? 0) + ' agents wired'); refreshAgents(); } }\nasync function detachAll() { if (!confirm('Detach Shadow Brain hooks from ALL agents?')) return; const r = await api('/api/v6/agents-detach-all', {}); if (r) { toast('Detach All', (r.attached?.length ?? 0) + ' agents', 'warn'); refreshAgents(); } }\n\n// \u2500\u2500\u2500\u2500 Memory browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction renderCountRows(rows, emptyMessage) {\n if (!rows || !rows.length) return '<div class=\"empty-tight\">' + htmlEscape(emptyMessage) + '</div>';\n return rows.slice(0, 8).map(row =>\n '<div class=\"row\"><span class=\"k\">' + htmlEscape(row.name) + '</span><span class=\"v\">' + htmlEscape(row.count) + '</span></div>'\n ).join('');\n}\nasync function refreshMemoryIntelligence() {\n const data = await api('/api/v6/memory-intelligence?limit=500');\n if (!data) return;\n document.getElementById('mem-total').textContent = data.totalEntries ?? 0;\n document.getElementById('mem-sampled').textContent = (data.sampledEntries ?? 0) + ' shown in intelligence sample';\n document.getElementById('mem-source').textContent = Math.round((data.sourceCoverage || 0) * 100) + '%';\n document.getElementById('mem-fresh').textContent = data.recent7d ?? 0;\n document.getElementById('mem-seed-open').textContent = data.openSeedDrafts ?? 0;\n document.getElementById('mem-seed-sub').textContent = (data.pendingApprovals ?? 0) + ' need approval';\n document.getElementById('mem-categories').innerHTML = renderCountRows(data.byCategory, 'No categories yet.');\n document.getElementById('mem-agents').innerHTML = renderCountRows(data.byAgent, 'No agent memory writes yet.');\n document.getElementById('mem-tags').innerHTML = renderCountRows(data.topTags, 'No tags yet.');\n}\nasync function searchMemory() {\n const r = await api('/api/v6/memory-browser', {\n query: document.getElementById('mem-query').value,\n agent: document.getElementById('mem-agent').value,\n category: document.getElementById('mem-category').value,\n minImportance: parseFloat(document.getElementById('mem-importance').value || '0'),\n });\n if (!r) return;\n document.getElementById('mem-results').innerHTML = r.length ? r.map(m =>\n `<tr><td><code>${m.id.slice(0, 10)}</code></td><td><span class=\"pill cyan\">${m.agentTool}</span></td><td>${m.category}</td><td>${(m.content || '').slice(0, 140)}</td><td>${(m.importance || 0).toFixed(2)}</td><td class=\"mono\">${new Date(m.createdAt).toLocaleDateString()}</td></tr>`\n ).join('') : '<tr><td colspan=\"6\"><div class=\"empty\">No memories match your filters</div></td></tr>';\n}\n\n// \u2500\u2500\u2500\u2500 Activity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Live Brain\n// Memory Seeder\nfunction seedProjectPath() {\n return document.getElementById('seed-project-path')?.value?.trim()\n || document.getElementById('live-project-path')?.value?.trim()\n || document.getElementById('ww2-project-path')?.value?.trim()\n || '';\n}\nfunction copyLivePathToSeeder() {\n const value = document.getElementById('live-project-path')?.value?.trim()\n || document.getElementById('ww2-project-path')?.value?.trim()\n || '';\n if (value) document.getElementById('seed-project-path').value = value;\n}\nfunction openSeederForLiveBrain() {\n const projectDir = liveProjectPath() || document.getElementById('ww2-project-path')?.value?.trim() || '';\n switchTab('seeder', 'Memory Seeder');\n if (projectDir) document.getElementById('seed-project-path').value = projectDir;\n const topic = document.getElementById('seed-topic');\n const objective = document.getElementById('seed-objective');\n if (topic && !topic.value) topic.value = 'Source-backed live brain improvement';\n if (objective && !objective.value) objective.value = 'Capture verified project knowledge that future agents should recall before complex work.';\n refreshSeedDrafts();\n}\nfunction seedDraftStatusClass(status) {\n if (status === 'seeded') return 'green';\n if (status === 'approved') return 'cyan';\n if (status === 'rejected') return 'red';\n return 'yellow';\n}\nfunction renderSeedStatus(drafts) {\n const counts = { draft: 0, approved: 0, seeded: 0, rejected: 0 };\n for (const d of drafts || []) counts[d.status] = (counts[d.status] || 0) + 1;\n return [\n '<div class=\"row\"><span class=\"k\">Open drafts</span><span class=\"v warn\">' + counts.draft + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Approved</span><span class=\"v good\">' + counts.approved + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Seeded</span><span class=\"v good\">' + counts.seeded + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Rejected</span><span class=\"v bad\">' + counts.rejected + '</span></div>',\n '<div class=\"row\"><span class=\"k\">Current project</span><span class=\"v mono\">' + htmlEscape(seedProjectPath() || 'dashboard default') + '</span></div>',\n ].join('');\n}\nfunction seedJsArg(value) {\n return JSON.stringify(String(value || ''));\n}\nfunction renderSeedDrafts(drafts) {\n if (!drafts || !drafts.length) return '<div class=\"empty-tight\">No seed drafts yet.</div>';\n return drafts.map(d => {\n const approvedBy = new Set((d.approvals || []).map(a => a.by));\n const required = d.requiredApprovals || [];\n const pending = required.filter(a => !approvedBy.has(a));\n const agentApprover = d.approverAgent && d.approverAgent !== 'user' ? d.approverAgent : d.researchAgent;\n const warnings = (d.warnings || []).length\n ? '<div class=\"warning-stack\" style=\"margin-top:8px\">' + d.warnings.map(w => '<div class=\"warning-banner\"><strong>Review</strong><span>' + htmlEscape(w) + '</span></div>').join('') + '</div>'\n : '';\n const sources = (d.sources || []).slice(0, 5).map(s => '<code>' + htmlEscape(s) + '</code>').join(' ');\n let actions = '';\n if (d.status === 'draft') {\n actions += '<button class=\"btn sm success\" onclick=\"approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg('user') + ')\">User Approve</button> ';\n actions += '<button class=\"btn sm\" onclick=\"approveSeedDraft(' + seedJsArg(d.id) + ',' + seedJsArg(agentApprover) + ')\">Agent Approve</button> ';\n actions += '<button class=\"btn sm danger\" onclick=\"rejectSeedDraft(' + seedJsArg(d.id) + ')\">Reject</button>';\n }\n if (d.status === 'approved') {\n actions += '<button class=\"btn sm primary\" onclick=\"commitSeedDraft(' + seedJsArg(d.id) + ')\">Seed to Brain</button> ';\n actions += '<button class=\"btn sm danger\" onclick=\"rejectSeedDraft(' + seedJsArg(d.id) + ')\">Reject</button>';\n }\n if (d.status === 'seeded') {\n actions += '<span class=\"pill green\">memory ' + htmlEscape((d.seededMemoryId || '').slice(0, 10)) + '</span> ';\n actions += '<button class=\"btn sm\" onclick=\"handoffToAgent(' + seedJsArg(d.researchAgent) + ')\" title=\"Generate a briefing to hand this knowledge back to an agent\">\u21A9 Hand to Agent</button>';\n }\n return '<div class=\"memory-item\">' +\n '<div class=\"meta\">' +\n '<span class=\"pill ' + seedDraftStatusClass(d.status) + '\">' + htmlEscape(d.status) + '</span>' +\n '<span class=\"pill purple\">' + htmlEscape(d.researchAgent) + '</span>' +\n '<span class=\"pill cyan\">' + htmlEscape(d.approvalMode) + '</span>' +\n '<span>' + htmlEscape(new Date(d.updatedAt || d.createdAt).toLocaleString()) + '</span>' +\n '</div>' +\n '<div style=\"display:flex;gap:12px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap\">' +\n '<div style=\"min-width:260px;flex:1\">' +\n '<div style=\"font-weight:700;margin-bottom:4px\">' + htmlEscape(d.topic) + '</div>' +\n '<div class=\"content\">' + htmlEscape(d.objective || '').slice(0, 260) + '</div>' +\n '<div class=\"mono\" style=\"font-size:11px;color:var(--text-3);margin-top:6px\">pending: ' + htmlEscape(pending.join(', ') || 'none') + ' | category: ' + htmlEscape(d.category) + '</div>' +\n '<div style=\"font-size:11px;color:var(--text-3);margin-top:6px\">' + (sources || 'No sources') + '</div>' +\n '</div>' +\n '<div style=\"display:flex;gap:6px;flex-wrap:wrap;justify-content:flex-end\">' + actions + '</div>' +\n '</div>' +\n warnings +\n '<pre class=\"mono\" style=\"margin-top:10px;max-height:180px;overflow:auto;background:var(--bg-elev-1);padding:10px;border-radius:6px;white-space:pre-wrap\">' + htmlEscape(d.content || '') + '</pre>' +\n '</div>';\n }).join('');\n}\nasync function refreshSeedDrafts() {\n const input = document.getElementById('seed-project-path');\n if (input && !input.value) input.value = seedProjectPath();\n const drafts = await api('/api/v6/seed-drafts?limit=50') ?? [];\n const open = drafts.filter(d => d.status === 'draft' || d.status === 'approved').length;\n const badge = document.getElementById('badge-seeder');\n if (badge) badge.textContent = open;\n const status = document.getElementById('seed-status');\n if (status) status.innerHTML = renderSeedStatus(drafts);\n const list = document.getElementById('seed-drafts');\n if (list) list.innerHTML = renderSeedDrafts(drafts);\n}\nasync function createSeedDraft() {\n const topic = document.getElementById('seed-topic').value.trim();\n if (!topic) { toast('Topic required', 'Add a seed topic before creating a draft.', 'warn'); return; }\n const draft = await api('/api/v6/seed-draft', {\n projectDir: seedProjectPath(),\n topic,\n objective: document.getElementById('seed-objective').value,\n researchAgent: document.getElementById('seed-research-agent').value,\n approverAgent: document.getElementById('seed-approver-agent').value,\n approvalMode: document.getElementById('seed-approval-mode').value,\n category: document.getElementById('seed-category').value,\n tags: document.getElementById('seed-tags').value,\n importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),\n sources: document.getElementById('seed-sources').value,\n content: document.getElementById('seed-content').value,\n });\n if (draft) {\n toast('Seed draft created', draft.status);\n refreshSeedDrafts();\n }\n}\n// One-click path: create \u2192 approve \u2192 save to brain. The \"single seed\".\nasync function quickSeed() {\n const topic = document.getElementById('seed-topic').value.trim();\n if (!topic) { toast('Topic required', 'Add a topic before quick-saving.', 'warn'); return; }\n const draft = await api('/api/v6/seed-draft', {\n projectDir: seedProjectPath(),\n topic,\n objective: document.getElementById('seed-objective').value,\n researchAgent: document.getElementById('seed-research-agent').value,\n approverAgent: 'user',\n approvalMode: 'user',\n category: document.getElementById('seed-category').value,\n tags: document.getElementById('seed-tags').value,\n importance: parseFloat(document.getElementById('seed-importance').value || '0.78'),\n sources: document.getElementById('seed-sources').value,\n content: document.getElementById('seed-content').value,\n });\n if (!draft || !draft.id) return;\n const approved = await api('/api/v6/seed-approve', { id: draft.id, approvedBy: 'user' });\n if (!approved) { toast('Saved as draft', 'Approve it in the queue to finish.', 'warn'); refreshSeedDrafts(); return; }\n const committed = await api('/api/v6/seed-commit', { id: draft.id, seededBy: 'user' });\n if (committed) {\n toast('Saved to brain \u26A1', 'Knowledge is now in memory.', 'success');\n refreshSeedDrafts();\n try { refreshLiveBrain(); } catch (e) {}\n } else {\n toast('Approved', 'Couldn\\'t finalize \u2014 finish from the queue.', 'warn');\n refreshSeedDrafts();\n }\n}\nasync function approveSeedDraft(id, approvedBy) {\n const draft = await api('/api/v6/seed-approve', { id, approvedBy });\n if (draft) {\n toast('Approval recorded', draft.status);\n refreshSeedDrafts();\n }\n}\nasync function rejectSeedDraft(id) {\n const reason = prompt('Reject reason', 'Not verified enough');\n if (reason === null) return;\n const draft = await api('/api/v6/seed-reject', { id, reason, rejectedBy: 'user' });\n if (draft) {\n toast('Draft rejected', draft.topic, 'warn');\n refreshSeedDrafts();\n }\n}\nasync function commitSeedDraft(id) {\n const draft = await api('/api/v6/seed-commit', { id, seededBy: 'user' });\n if (draft) {\n toast('Seeded to brain', (draft.seededMemoryId || '').slice(0, 10));\n refreshSeedDrafts();\n refreshLiveBrain();\n }\n}\n// Two-way: hand the brain's knowledge back to an agent as a ready briefing.\nasync function handoffToAgent(toAgent) {\n const md = await api('/api/v6/agent-handoff', { projectDir: seedProjectPath(), fromAgent: 'user', toAgent: toAgent || 'claude-code', format: 'markdown' });\n if (!md) return;\n try {\n await navigator.clipboard.writeText(typeof md === 'string' ? md : JSON.stringify(md, null, 2));\n toast('Handoff copied', 'Briefing for ' + (toAgent || 'agent') + ' is on your clipboard \u2014 paste it into the agent.', 'success');\n } catch (e) {\n toast('Handoff ready', 'Copy it from the Activity log if clipboard is blocked.', 'warn');\n }\n}\n\nfunction liveProjectPath() {\n return document.getElementById('live-project-path')?.value?.trim() || '';\n}\nfunction liveActionPill(priority) {\n if (priority === 'critical' || priority === 'high') return 'red';\n if (priority === 'medium') return 'yellow';\n return 'cyan';\n}\nasync function refreshLiveBrain() {\n const projectDir = liveProjectPath();\n const queries = document.getElementById('live-queries')?.value?.trim() || '';\n const url = '/api/v6/live-brain?projectDir=' + encodeURIComponent(projectDir) + (queries ? '&queries=' + encodeURIComponent(queries) : '');\n const data = await api(url);\n if (!data) return;\n\n const quality = data.memoryQuality || {};\n document.getElementById('live-memories').textContent = quality.totalEntries ?? 0;\n document.getElementById('live-quality').textContent = (quality.avgScore ?? 0) + '/100';\n document.getElementById('live-sources').textContent = Math.round((quality.sourceCoverage ?? 0) * 100) + '%';\n document.getElementById('live-recall').textContent = data.recallEval?.avgHitCount ?? 0;\n document.getElementById('live-scope').textContent = data.projectScoped ? 'project scoped' : 'global fallback';\n\n document.getElementById('live-actions').innerHTML = (data.learningActions || []).length\n ? data.learningActions.map(a => `<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill ${liveActionPill(a.priority)}\">${a.priority}</span><span>${a.title}</span></div><div class=\"content\">${a.reason}</div>${a.command ? '<code>' + a.command + '</code>' : ''}</div>`).join('')\n : '<div class=\"empty\">No immediate hygiene actions.</div>';\n\n document.getElementById('live-eval').innerHTML = (data.recallEval?.cases || []).map(c =>\n `<div class=\"row\"><span class=\"k\">${c.query}</span><span class=\"v ${c.hitCount ? 'good' : 'bad'}\">${c.hitCount} hits - ${c.sourceBackedHits} sourced</span></div>`\n ).join('') || '<div class=\"empty\">No eval cases.</div>';\n\n document.getElementById('live-top').innerHTML = (data.topMemories || []).slice(0, 8).map(m =>\n `<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill purple\">${m.agentTool}</span><span>${m.category}</span><span>score ${m.qualityScore}/100</span></div><div class=\"content\">${(m.content || '').slice(0, 260)}</div></div>`\n ).join('') || '<div class=\"empty\">No memories.</div>';\n\n document.getElementById('live-brief').textContent = data.agentBriefing || '';\n}\n\n// \u2500\u2500\u2500\u2500 Realtime Activity Stream (v6.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet activityBuffer = [];\nconst activitySeen = new Set();\nconst activitySeenOrder = [];\nlet activityFilter = '';\nlet activityPaused = false;\nlet activityRate = [];\nlet lastIngestFresh = false;\n\nfunction activityKey(e) { return (e.type || '') + '|' + (e.source || '') + '|' + String(e.detail || '').slice(0, 60); }\nfunction actEpoch(e) {\n if (typeof e.ts === 'number') return e.ts;\n if (e.timestamp) { const t = Date.parse(e.timestamp); if (!isNaN(t)) return t; }\n return Date.now();\n}\nfunction ingestActivity(raw, fresh) {\n const e = { ts: actEpoch(raw), type: raw.type || 'event', source: raw.source || 'brain', detail: String(raw.detail || '') };\n const key = activityKey(e);\n if (activitySeen.has(key)) return false;\n activitySeen.add(key); activitySeenOrder.push(key);\n if (activitySeenOrder.length > 1200) { const drop = activitySeenOrder.splice(0, activitySeenOrder.length - 1200); for (const k of drop) activitySeen.delete(k); }\n activityBuffer.unshift(e);\n if (activityBuffer.length > 500) activityBuffer.length = 500;\n activityRate.push(e.ts);\n const cutoff = Date.now() - 30 * 60 * 1000;\n while (activityRate.length && activityRate[0] < cutoff) activityRate.shift();\n if (!activityPaused) {\n appendLog(e.source, e.type, e.detail);\n if (fresh) {\n try {\n if (typeof graphTimer !== 'undefined' && graphTimer) {\n const col = signalColors[e.type] || '#18ffff';\n const srcExists = graphNodes.find(n => n.id === e.source);\n graphSignals.push({ fromId: srcExists ? e.source : 'brain', toId: 'brain', type: e.type, color: col, progress: 0, spawnedAt: Date.now() });\n if (graphSignals.length > 100) graphSignals = graphSignals.slice(-100);\n }\n } catch (err) {}\n lastIngestFresh = true;\n }\n }\n return true;\n}\nfunction typeMatchesFilter(type, filter) {\n if (!filter) return true;\n if (filter === 'seed-draft') return type === 'seed-draft' || type === 'seed-commit';\n return type === filter;\n}\nfunction renderActivityTable() {\n const tbody = document.getElementById('activity-table');\n if (!tbody) return;\n const searchEl = document.getElementById('act-search');\n const q = (searchEl ? searchEl.value : '').toLowerCase().trim();\n let rows = activityBuffer.filter(e => typeMatchesFilter(e.type, activityFilter));\n if (q) rows = rows.filter(e => (e.source + ' ' + e.type + ' ' + e.detail).toLowerCase().includes(q));\n const fresh = lastIngestFresh; lastIngestFresh = false;\n rows = rows.slice(0, 200);\n tbody.innerHTML = rows.length\n ? rows.map((e, i) => '<tr' + (i === 0 && fresh ? ' class=\"act-fresh\"' : '') + '><td class=\"mono\">' + new Date(e.ts).toLocaleTimeString() + '</td><td><span class=\"pill purple\">' + htmlEscape(e.source) + '</span></td><td><span class=\"pill ' + typeToPill(e.type) + '\">' + htmlEscape(e.type) + '</span></td><td class=\"mono\">' + htmlEscape(e.detail.slice(0, 200)) + '</td></tr>').join('')\n : '<tr><td colspan=\"4\"><div class=\"empty\">No activity</div></td></tr>';\n const total = document.getElementById('act-stat-total'); if (total) total.textContent = activityBuffer.length;\n const last = document.getElementById('act-stat-last'); if (last) last.textContent = activityBuffer[0] ? new Date(activityBuffer[0].ts).toLocaleTimeString() : '\u2014';\n}\nfunction eventsPerMin() { const c = Date.now() - 60000; return activityRate.filter(t => t >= c).length; }\nfunction renderSparkline() {\n const rate = eventsPerMin();\n const ratePill = document.getElementById('act-rate-pill'); if (ratePill) ratePill.textContent = rate + ' / min';\n const ovRate = document.getElementById('ov-pulse-rate'); if (ovRate) ovRate.textContent = rate + ' / min';\n const cv = document.getElementById('act-spark');\n if (!cv || !cv.getContext) return;\n const now = Date.now();\n const buckets = new Array(10).fill(0);\n for (const t of activityRate) { const age = now - t; if (age < 0 || age > 10 * 60000) continue; const idx = 9 - Math.floor(age / 60000); if (idx >= 0 && idx < 10) buckets[idx]++; }\n const pw = (cv.parentElement ? cv.parentElement.clientWidth : 0) || 600;\n cv.width = pw; cv.height = 120;\n const ctx = cv.getContext('2d'); const W = cv.width, H = cv.height; ctx.clearRect(0, 0, W, H);\n let max = 1; for (const b of buckets) if (b > max) max = b;\n const bw = W / 10;\n for (let i = 0; i < 10; i++) {\n const h = (buckets[i] / max) * (H - 12);\n const x = i * bw + 3, y = H - h;\n ctx.fillStyle = i === 9 ? '#18ffff' : 'rgba(168,85,247,0.55)';\n ctx.fillRect(x, y, bw - 6, Math.max(0, h));\n }\n}\nfunction renderLivePulse() {\n const el = document.getElementById('ov-pulse');\n if (!el) return;\n const items = activityBuffer.slice(0, 8);\n el.innerHTML = items.length\n ? items.map(e => '<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill ' + typeToPill(e.type) + '\">' + htmlEscape(e.type) + '</span><span class=\"pill purple\">' + htmlEscape(e.source) + '</span><span>' + new Date(e.ts).toLocaleTimeString() + '</span></div><div class=\"content\">' + htmlEscape(e.detail.slice(0, 200)) + '</div></div>').join('')\n : '<div class=\"empty-tight\">Awaiting live activity\u2026</div>';\n}\nfunction setActivityFilter(btn, type) {\n activityFilter = type;\n const chips = document.querySelectorAll('#act-chips .chip');\n chips.forEach(c => c.classList.toggle('active', c === btn));\n renderActivityTable();\n}\nfunction toggleActivityPause() {\n activityPaused = !activityPaused;\n const btn = document.getElementById('act-pause-btn'); if (btn) btn.textContent = activityPaused ? 'Resume' : 'Pause';\n const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', activityPaused); badge.textContent = activityPaused ? 'PAUSED' : 'LIVE'; }\n const live = document.getElementById('act-stat-live'); if (live) { live.textContent = activityPaused ? 'paused' : 'streaming'; live.className = 'v ' + (activityPaused ? 'warn' : 'good'); }\n if (!activityPaused) renderActivityTable();\n}\nfunction clearActivity() {\n activityBuffer = []; activitySeen.clear(); activitySeenOrder.length = 0; activityRate = [];\n renderActivityTable(); renderSparkline(); renderLivePulse();\n}\nfunction setConn(online) {\n const d = document.getElementById('conn-dot'); if (d) { d.classList.toggle('offline', !online); d.title = 'Realtime connection: ' + (online ? 'live' : 'reconnecting'); }\n if (activityPaused) return;\n const badge = document.getElementById('act-live-badge'); if (badge) { badge.classList.toggle('paused', !online); badge.textContent = online ? 'LIVE' : 'OFFLINE'; }\n const live = document.getElementById('act-stat-live'); if (live) { live.textContent = online ? 'streaming' : 'reconnecting'; live.className = 'v ' + (online ? 'good' : 'bad'); }\n}\nasync function refreshActivity() {\n const log = await api('/api/v6/activity-log?limit=200');\n if (Array.isArray(log)) for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);\n renderActivityTable(); renderSparkline();\n}\n\n// \u2500\u2500\u2500\u2500 Live Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet graphFrame = 0, graphNodes = [], graphEdges = [], graphSignals = [], graphTimer = null, graphPullTimer = null;\nconst signalColors = { 'memory-write':'#10e68c','subagent-spawn':'#18ffff','dream':'#a855f7','revisit':'#a855f7','counterfactual':'#a855f7','consolidation':'#a855f7','contradiction':'#ff5864','pattern-discovery':'#10e68c','collision':'#ff5864','quarantine':'#ec4899','firewall-block':'#ffb53f' };\nasync function startGraph() { await pullTopology(); if (graphTimer) cancelAnimationFrame(graphTimer); graphLoop(); if (graphPullTimer) clearInterval(graphPullTimer); graphPullTimer = setInterval(pullTopology, 3000); }\nasync function pullTopology() {\n const topo = await api('/api/v6/topology', {});\n if (!topo) return;\n const canvas = document.getElementById('graph-canvas');\n if (!canvas) return;\n const W = canvas.width, H = canvas.height, cx = W/2, cy = H/2;\n const prev = new Map(graphNodes.map(n => [n.id, { x: n.x, y: n.y }]));\n graphNodes = [{ id:'brain', label:'HIVE MIND', x: cx, y: cy, r: 50, type:'brain', color:'#a855f7' }];\n (topo.agents || []).forEach((a, i, arr) => {\n const ang = (i / arr.length) * Math.PI * 2 - Math.PI / 2, r = 240;\n const p = prev.get(a.id);\n graphNodes.push({ id: a.id, label: a.displayName, x: p?.x ?? cx + Math.cos(ang) * r, y: p?.y ?? cy + Math.sin(ang) * r, targetX: cx + Math.cos(ang) * r, targetY: cy + Math.sin(ang) * r, r: a.connected ? 24 : 16, type:'agent', connected: a.connected, color: a.connected ? '#18ffff' : '#556080' });\n });\n (topo.subAgents || []).forEach((s, i, arr) => {\n const parent = graphNodes.find(n => n.id === s.parent), ang = (i / Math.max(arr.length,1)) * Math.PI * 2, r = 130;\n const p = prev.get(s.id);\n graphNodes.push({ id: s.id, label: (s.task || '').slice(0, 18), x: p?.x ?? (parent?.x ?? cx) + Math.cos(ang) * 40, y: p?.y ?? (parent?.y ?? cy) + Math.sin(ang) * 40, targetX: cx + Math.cos(ang) * r, targetY: cy + Math.sin(ang) * r, r: 12, type:'subagent', parent: s.parent, color: '#a855f7' });\n });\n graphEdges = [];\n for (const n of graphNodes) {\n if (n.type === 'agent') graphEdges.push({ from:'brain', to: n.id, active: n.connected });\n if (n.type === 'subagent') graphEdges.push({ from: n.parent ?? 'brain', to: n.id, active: true });\n }\n const now = Date.now();\n for (const e of (topo.events || [])) {\n const src = e.source ?? 'brain';\n const srcN = graphNodes.find(n => n.id === src) ?? graphNodes[0];\n const dstN = e.type === 'subagent-spawn' ? graphNodes.find(n => n.id === e.target) : graphNodes.find(n => n.id === 'brain');\n if (!srcN || !dstN) continue;\n graphSignals.push({ fromId: srcN.id, toId: dstN.id, type: e.type, color: signalColors[e.type] || '#18ffff', progress: 0, spawnedAt: now });\n }\n if (graphSignals.length > 100) graphSignals = graphSignals.slice(-100);\n document.getElementById('graph-meta').textContent = `${(topo.agents || []).length} agents \u00B7 ${(topo.subAgents || []).length} sub-agents \u00B7 ${graphSignals.length} signals`;\n}\nfunction graphLoop() {\n const c = document.getElementById('graph-canvas');\n if (!c) return;\n const ctx = c.getContext('2d'), W = c.width, H = c.height;\n const now = Date.now();\n ctx.clearRect(0, 0, W, H);\n // Ambient backdrop with a soft vignette.\n const grd = ctx.createRadialGradient(W/2, H/2, 40, W/2, H/2, Math.max(W,H)/1.4);\n grd.addColorStop(0, 'rgba(168,85,247,0.10)'); grd.addColorStop(0.5, 'rgba(24,255,255,0.03)'); grd.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = grd; ctx.fillRect(0, 0, W, H);\n\n for (const n of graphNodes) if (n.targetX != null) { n.x += (n.targetX - n.x) * 0.08; n.y += (n.targetY - n.y) * 0.08; }\n\n // \u2500\u2500 Edges: curved, with a gentle animated dash flow on active links \u2500\u2500\u2500\u2500\u2500\u2500\n for (const e of graphEdges) {\n const a = graphNodes.find(n => n.id === e.from), b = graphNodes.find(n => n.id === e.to);\n if (!a || !b) continue;\n const mx = (a.x + b.x) / 2, my = (a.y + b.y) / 2;\n // Curve control point nudged perpendicular for an organic arc.\n const dx = b.x - a.x, dy = b.y - a.y, len = Math.sqrt(dx*dx + dy*dy) || 1;\n const cxp = mx - (dy / len) * 26, cyp = my + (dx / len) * 26;\n if (e.active) {\n const g = ctx.createLinearGradient(a.x, a.y, b.x, b.y);\n g.addColorStop(0, 'rgba(24,255,255,0.05)'); g.addColorStop(0.5, 'rgba(24,255,255,0.34)'); g.addColorStop(1, 'rgba(168,85,247,0.18)');\n ctx.strokeStyle = g; ctx.lineWidth = 1.6;\n ctx.setLineDash([5, 7]); ctx.lineDashOffset = -(now / 45) % 12;\n } else {\n ctx.strokeStyle = 'rgba(96,108,140,0.14)'; ctx.lineWidth = 1; ctx.setLineDash([3, 5]); ctx.lineDashOffset = 0;\n }\n ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.quadraticCurveTo(cxp, cyp, b.x, b.y); ctx.stroke();\n }\n ctx.setLineDash([]); ctx.lineDashOffset = 0;\n\n // \u2500\u2500 Signals: glowing head + fading motion trail \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n graphSignals = graphSignals.filter(s => now - s.spawnedAt < 3500);\n for (const s of graphSignals) {\n const from = graphNodes.find(n => n.id === s.fromId), to = graphNodes.find(n => n.id === s.toId);\n if (!from || !to) continue;\n s.progress = Math.min(1, (now - s.spawnedAt) / 2400);\n const t = s.progress;\n const ease = t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2;\n // Trail: several fading dots behind the head.\n for (let k = 0; k < 5; k++) {\n const tt = Math.max(0, ease - k * 0.045);\n const x = from.x + (to.x - from.x) * tt, y = from.y + (to.y - from.y) * tt;\n const alpha = (1 - k / 5) * (1 - Math.abs(t - 0.5)) * 0.9;\n ctx.globalAlpha = Math.max(0, alpha);\n ctx.fillStyle = s.color;\n ctx.beginPath(); ctx.arc(x, y, 3.2 - k * 0.4, 0, Math.PI * 2); ctx.fill();\n }\n ctx.globalAlpha = 1;\n const hx = from.x + (to.x - from.x) * ease, hy = from.y + (to.y - from.y) * ease;\n const tail = ctx.createRadialGradient(hx, hy, 0, hx, hy, 20);\n tail.addColorStop(0, s.color); tail.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = tail; ctx.beginPath(); ctx.arc(hx, hy, 18, 0, Math.PI * 2); ctx.fill();\n ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(hx, hy, 2.2, 0, Math.PI * 2); ctx.fill();\n }\n\n // \u2500\u2500 Nodes: layered glow, ring, core, label chip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n for (const n of graphNodes) {\n const isBrain = n.type === 'brain';\n const pulse = isBrain ? Math.sin(graphFrame / 15) * 4 : Math.sin((graphFrame + (n.x|0)) / 22) * 1.5;\n const r = n.r + pulse;\n const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.8);\n halo.addColorStop(0, n.color + '77'); halo.addColorStop(0.6, n.color + '1a'); halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.8, 0, Math.PI * 2); ctx.fill();\n // Rotating accent ring on the brain hub.\n if (isBrain) {\n ctx.save(); ctx.translate(n.x, n.y); ctx.rotate((now / 1600) % (Math.PI * 2));\n ctx.strokeStyle = n.color + '88'; ctx.lineWidth = 1.5; ctx.setLineDash([3, 9]);\n ctx.beginPath(); ctx.arc(0, 0, r + 10, 0, Math.PI * 2); ctx.stroke();\n ctx.restore(); ctx.setLineDash([]);\n }\n const fill = ctx.createRadialGradient(n.x - r*0.3, n.y - r*0.3, r*0.2, n.x, n.y, r);\n fill.addColorStop(0, isBrain ? '#1b2142' : '#121a30'); fill.addColorStop(1, isBrain ? '#0a0e1f' : '#080c16');\n ctx.fillStyle = fill;\n ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = n.color; ctx.lineWidth = isBrain ? 2.5 : 1.8; ctx.stroke();\n ctx.fillStyle = n.color; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.34), 0, Math.PI * 2); ctx.fill();\n // Label\n ctx.fillStyle = n.color;\n ctx.font = isBrain ? '800 13px Inter, sans-serif' : '600 11px Inter, sans-serif';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText(n.label, n.x, isBrain ? n.y : n.y + r + 13);\n }\n graphFrame++;\n graphTimer = requestAnimationFrame(graphLoop);\n}\n\n// \u2500\u2500\u2500\u2500 Memory Constellation (Intelligence hub) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet constNodes = [], constEdges = [], constStats = null, constIdMap = new Map();\nlet constView = { zoom: 1, panX: 0, panY: 0 };\nlet constSim = { running: false, alpha: 1, raf: null };\nlet constFilter = '', constFocus = null, constHover = null;\nlet constDragNode = null, constPanning = false, constLoaded = false, constMoved = false;\nlet constLast = { x: 0, y: 0 };\nlet constLiveTimer = null;\nconst NODE_COLORS = { memory: '#18ffff', project: '#10e68c', agent: '#ffb53f', tag: '#a855f7' };\n\nasync function refreshIntelligence() {\n bindConstellation();\n refreshIntelAnalytics();\n if (!constLoaded) { await loadConstellation(); }\n else { sizeConstCanvas(); startConstSim(); }\n}\n\nasync function loadConstellation() {\n const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');\n applyConstellation(snap);\n constLoaded = true;\n}\n\nfunction applyConstellation(snap) {\n const cv = document.getElementById('const-canvas');\n const empty = document.getElementById('const-empty');\n constNodes = []; constEdges = []; constIdMap = new Map();\n const nodes = (snap && snap.nodes) || [];\n constStats = (snap && snap.stats) || null;\n if (empty) empty.classList.toggle('show', nodes.length === 0);\n const badge = document.getElementById('badge-intelligence');\n if (badge) badge.textContent = constStats ? constStats.memories : 0;\n if (!nodes.length) { drawConstellation(); return; }\n sizeConstCanvas();\n const W = cv ? cv.width : 800, H = cv ? cv.height : 600, cx = W / 2, cy = H / 2;\n for (let i = 0; i < nodes.length; i++) {\n const n = nodes[i];\n const ang = (i / nodes.length) * Math.PI * 2;\n const rad = 60 + Math.random() * Math.min(W, H) * 0.32;\n constNodes.push({\n id: n.id, label: n.label || '', type: n.type, score: n.score || 0,\n size: n.size || 9, degree: n.degree || 0, meta: n.meta || {},\n x: cx + Math.cos(ang) * rad, y: cy + Math.sin(ang) * rad, vx: 0, vy: 0,\n });\n }\n for (const node of constNodes) constIdMap.set(node.id, node);\n for (const e of (snap.edges || [])) {\n const sn = constIdMap.get(e.source), tn = constIdMap.get(e.target);\n if (sn && tn) constEdges.push({ sn: sn, tn: tn, w: e.weight || 1, type: e.type });\n }\n const meta = document.getElementById('const-meta');\n if (meta && constStats) meta.textContent = constStats.memories + ' mem \u00B7 ' + constStats.projects + ' proj \u00B7 ' + constStats.agents + ' agent \u00B7 ' + constStats.tags + ' tag \u00B7 ' + constStats.edges + ' links' + (constStats.droppedNodes ? ' (+' + constStats.droppedNodes + ' hidden)' : '');\n updateConstHero();\n constView = { zoom: 1, panX: 0, panY: 0 };\n constSim.alpha = 1;\n startConstSim();\n}\n\nfunction sizeConstCanvas() {\n const cv = document.getElementById('const-canvas'), wrap = document.getElementById('const-wrap');\n if (!cv || !wrap) return;\n cv.width = wrap.clientWidth || 800;\n cv.height = wrap.clientHeight || 600;\n}\n\nfunction startConstSim() {\n if (constSim.running || !constNodes.length) return;\n constSim.running = true;\n constSim.alpha = Math.max(constSim.alpha, 0.5);\n constLoop();\n}\n\nfunction constLoop() {\n const sec = document.getElementById('tab-intelligence');\n if (!sec || !sec.classList.contains('active')) { constSim.running = false; constSim.raf = null; return; }\n stepConstPhysics();\n drawConstellation();\n constSim.raf = requestAnimationFrame(constLoop);\n}\n\nfunction stepConstPhysics() {\n const cv = document.getElementById('const-canvas'); if (!cv) return;\n const W = cv.width, H = cv.height, cx = W / 2, cy = H / 2;\n const a = constSim.alpha;\n const N = constNodes.length;\n if (a > 0.01) {\n for (let i = 0; i < N; i++) {\n const ni = constNodes[i];\n for (let j = i + 1; j < N; j++) {\n const nj = constNodes[j];\n let dx = ni.x - nj.x, dy = ni.y - nj.y;\n let d2 = dx * dx + dy * dy; if (d2 < 1) d2 = 1;\n const d = Math.sqrt(d2);\n let rep = (3600 * a) / d2;\n // Hard separation: keep node circles from visually overlapping.\n const minD = ni.size + nj.size + 12;\n if (d < minD) rep += (minD - d) * 0.28 * a;\n const ux = dx / d, uy = dy / d;\n ni.vx += ux * rep; ni.vy += uy * rep;\n nj.vx -= ux * rep; nj.vy -= uy * rep;\n }\n }\n for (const e of constEdges) {\n let dx = e.tn.x - e.sn.x, dy = e.tn.y - e.sn.y;\n let d = Math.sqrt(dx * dx + dy * dy) || 1;\n const ideal = 94;\n const f = ((d - ideal) * 0.015 * a) * Math.min(2, e.w);\n const ux = dx / d, uy = dy / d;\n e.sn.vx += ux * f; e.sn.vy += uy * f;\n e.tn.vx -= ux * f; e.tn.vy -= uy * f;\n }\n for (const n of constNodes) {\n if (n === constDragNode) continue;\n n.vx += (cx - n.x) * 0.010 * a;\n n.vy += (cy - n.y) * 0.010 * a;\n n.vx *= 0.86; n.vy *= 0.86;\n const sp = Math.sqrt(n.vx * n.vx + n.vy * n.vy);\n if (sp > 20) { n.vx = n.vx / sp * 20; n.vy = n.vy / sp * 20; }\n n.x += n.vx; n.y += n.vy;\n }\n constSim.alpha *= 0.99;\n if (constSim.alpha < 0.01) constSim.alpha = 0.01;\n }\n}\n\nfunction constNodeOpacity(n) {\n if (constFilter && n.type !== constFilter) return 0.12;\n if (constFocus) {\n if (n.id === constFocus) return 1;\n const f = constIdMap.get(constFocus);\n if (f && f._neighbors && f._neighbors.has(n.id)) return 0.95;\n return 0.14;\n }\n return 1;\n}\n\nfunction drawConstellation() {\n const cv = document.getElementById('const-canvas'); if (!cv || !cv.getContext) return;\n const ctx = cv.getContext('2d'), W = cv.width, H = cv.height;\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.clearRect(0, 0, W, H);\n ctx.translate(constView.panX, constView.panY);\n ctx.scale(constView.zoom, constView.zoom);\n\n for (const e of constEdges) {\n const oa = Math.min(constNodeOpacity(e.sn), constNodeOpacity(e.tn));\n const lit = constFocus && (e.sn.id === constFocus || e.tn.id === constFocus);\n ctx.strokeStyle = lit ? 'rgba(24,255,255,' + (0.55 * oa) + ')' : 'rgba(120,140,180,' + (0.16 * oa) + ')';\n ctx.lineWidth = lit ? 1.6 : 0.7;\n ctx.beginPath(); ctx.moveTo(e.sn.x, e.sn.y); ctx.lineTo(e.tn.x, e.tn.y); ctx.stroke();\n }\n for (const n of constNodes) {\n const op = constNodeOpacity(n);\n const col = NODE_COLORS[n.type] || '#9aa7c0';\n const r = n.size;\n const halo = ctx.createRadialGradient(n.x, n.y, r * 0.4, n.x, n.y, r * 2.4);\n halo.addColorStop(0, col + Math.round(op * 90).toString(16).padStart(2, '0'));\n halo.addColorStop(1, 'rgba(0,0,0,0)');\n ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(n.x, n.y, r * 2.4, 0, Math.PI * 2); ctx.fill();\n ctx.globalAlpha = op;\n ctx.fillStyle = '#0b1020'; ctx.beginPath(); ctx.arc(n.x, n.y, r, 0, Math.PI * 2); ctx.fill();\n ctx.strokeStyle = col; ctx.lineWidth = (n.id === constFocus || n.id === constHover) ? 3 : 1.6;\n ctx.stroke();\n ctx.fillStyle = col; ctx.beginPath(); ctx.arc(n.x, n.y, Math.max(2, r * 0.42), 0, Math.PI * 2); ctx.fill();\n const showLabel = r > 15 || n.id === constHover || n.id === constFocus || n.type !== 'memory';\n if (showLabel && op > 0.4) {\n ctx.globalAlpha = op;\n ctx.fillStyle = document.documentElement.dataset.theme === 'light' ? '#1a2030' : '#e8ecf4';\n ctx.font = (n.id === constFocus ? 'bold ' : '') + Math.max(10, Math.min(14, 9 + r * 0.18)) + 'px Inter, sans-serif';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n const lbl = n.label.length > 26 ? n.label.slice(0, 25) + '\u2026' : n.label;\n ctx.fillText(lbl, n.x, n.y + r + 9);\n }\n ctx.globalAlpha = 1;\n }\n}\n\nfunction constMouseWorld(e) {\n const cv = document.getElementById('const-canvas'); const rect = cv.getBoundingClientRect();\n const sx = (e.clientX - rect.left) * (cv.width / rect.width);\n const sy = (e.clientY - rect.top) * (cv.height / rect.height);\n return { sx: sx, sy: sy, wx: (sx - constView.panX) / constView.zoom, wy: (sy - constView.panY) / constView.zoom };\n}\nfunction constHitTest(wx, wy) {\n for (let i = constNodes.length - 1; i >= 0; i--) {\n const n = constNodes[i];\n const dx = wx - n.x, dy = wy - n.y;\n if (dx * dx + dy * dy <= (n.size + 4) * (n.size + 4)) return n;\n }\n return null;\n}\nfunction computeNeighbors(id) {\n const n = constIdMap.get(id); if (!n) return;\n if (n._neighbors) return;\n const set = new Set();\n for (const e of constEdges) { if (e.sn.id === id) set.add(e.tn.id); if (e.tn.id === id) set.add(e.sn.id); }\n n._neighbors = set;\n}\nfunction openConstDrawer(n) {\n const drawer = document.getElementById('const-drawer'), body = document.getElementById('const-drawer-body');\n if (!drawer || !body) return;\n const m = n.meta || {};\n let h = '<div class=\"cd-type\" style=\"color:' + (NODE_COLORS[n.type] || '#9aa7c0') + '\">' + n.type + '</div>';\n h += '<div class=\"cd-title\">' + htmlEscape(n.label) + '</div>';\n h += '<div class=\"row\"><span class=\"k\">Importance</span><span class=\"v\">' + (n.score * 100).toFixed(0) + '/100</span></div>';\n h += '<div class=\"row\"><span class=\"k\">Connections</span><span class=\"v\">' + n.degree + '</span></div>';\n if (n.type === 'memory') {\n if (m.category) h += '<div class=\"row\"><span class=\"k\">Category</span><span class=\"v\">' + htmlEscape(String(m.category)) + '</span></div>';\n if (m.agentTool) h += '<div class=\"row\"><span class=\"k\">Agent</span><span class=\"v\">' + htmlEscape(String(m.agentTool)) + '</span></div>';\n if (m.projectName) h += '<div class=\"row\"><span class=\"k\">Project</span><span class=\"v\">' + htmlEscape(String(m.projectName)) + '</span></div>';\n if (m.content) h += '<div class=\"cd-section\"><div class=\"cd-label\">Content</div><div class=\"cd-content\">' + htmlEscape(String(m.content)) + '</div></div>';\n const tags = Array.isArray(m.tags) ? m.tags : [];\n if (tags.length) h += '<div class=\"cd-section\"><div class=\"cd-label\">Tags</div><div class=\"tag-cloud\">' + tags.map(function (t) { return '<span class=\"tc\">#' + htmlEscape(String(t)) + '</span>'; }).join('') + '</div></div>';\n const src = Array.isArray(m.sources) ? m.sources : [];\n if (src.length) h += '<div class=\"cd-section\"><div class=\"cd-label\">Sources</div><div class=\"cd-content mono\">' + src.map(function (s) { return htmlEscape(String(s)); }).join('<br>') + '</div></div>';\n } else {\n h += '<div class=\"cd-section\"><div class=\"cd-content\">This ' + n.type + ' node links ' + n.degree + ' memories in the constellation. Click connected nodes to explore.</div></div>';\n }\n body.innerHTML = h;\n drawer.classList.add('open');\n}\nfunction closeConstDrawer() {\n const drawer = document.getElementById('const-drawer'); if (drawer) drawer.classList.remove('open');\n constFocus = null;\n}\n\nfunction bindConstellation() {\n const wrap = document.getElementById('const-wrap'); if (!wrap || wrap._bound) return;\n wrap._bound = true;\n const cv = document.getElementById('const-canvas');\n wrap.addEventListener('wheel', function (e) {\n e.preventDefault();\n const p = constMouseWorld(e);\n const factor = e.deltaY < 0 ? 1.12 : 0.89;\n const nz = Math.max(0.25, Math.min(4, constView.zoom * factor));\n constView.panX = p.sx - p.wx * nz;\n constView.panY = p.sy - p.wy * nz;\n constView.zoom = nz;\n if (!constSim.running) drawConstellation();\n }, { passive: false });\n wrap.addEventListener('mousedown', function (e) {\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n constMoved = false; constLast = { x: e.clientX, y: e.clientY };\n if (hit) { constDragNode = hit; constSim.alpha = Math.max(constSim.alpha, 0.4); startConstSim(); }\n else { constPanning = true; wrap.classList.add('dragging'); }\n });\n window.addEventListener('mousemove', function (e) {\n if (constDragNode) {\n const p = constMouseWorld(e); constDragNode.x = p.wx; constDragNode.y = p.wy; constDragNode.vx = 0; constDragNode.vy = 0; constMoved = true;\n return;\n }\n if (constPanning) {\n constView.panX += e.clientX - constLast.x; constView.panY += e.clientY - constLast.y;\n constLast = { x: e.clientX, y: e.clientY }; constMoved = true;\n if (!constSim.running) drawConstellation();\n return;\n }\n const sec = document.getElementById('tab-intelligence');\n if (!sec || !sec.classList.contains('active')) return;\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n const tip = document.getElementById('const-tip');\n constHover = hit ? hit.id : null;\n if (hit && tip) {\n tip.innerHTML = '<b style=\"color:' + (NODE_COLORS[hit.type] || '#fff') + '\">' + htmlEscape(hit.label) + '</b><br>' + hit.type + ' \u00B7 ' + (hit.score * 100).toFixed(0) + '/100 \u00B7 ' + hit.degree + ' links';\n const rect = cv.getBoundingClientRect();\n tip.style.left = (e.clientX - rect.left) + 'px'; tip.style.top = (e.clientY - rect.top) + 'px';\n tip.classList.add('show');\n } else if (tip) tip.classList.remove('show');\n });\n window.addEventListener('mouseup', function (e) {\n if (constDragNode && !constMoved) { constFocus = constDragNode.id; computeNeighbors(constFocus); openConstDrawer(constDragNode); }\n else if (constPanning && !constMoved) {\n const p = constMouseWorld(e); const hit = constHitTest(p.wx, p.wy);\n if (!hit) { constFocus = null; closeConstDrawer(); }\n }\n constDragNode = null; constPanning = false; wrap.classList.remove('dragging');\n });\n}\nfunction setConstFilter(btn, type) {\n constFilter = type;\n const chips = document.querySelectorAll('#const-chips .chip');\n chips.forEach(function (c) { c.classList.toggle('active', c === btn); });\n if (!constSim.running) drawConstellation();\n}\nfunction highlightConstSearch() {\n const q = (document.getElementById('const-search').value || '').toLowerCase().trim();\n if (!q) { constFocus = null; if (!constSim.running) drawConstellation(); return; }\n const hit = constNodes.find(function (n) { return n.label.toLowerCase().includes(q); });\n if (hit) { constFocus = hit.id; computeNeighbors(hit.id); openConstDrawer(hit); }\n if (!constSim.running) drawConstellation();\n}\nfunction rebuildConstellation() { constLoaded = false; toast('Rebuilding', 'Recomputing the memory constellation\u2026', 'info'); loadConstellation(); }\nfunction resetConstellationView() { constView = { zoom: 1, panX: 0, panY: 0 }; constFocus = null; closeConstDrawer(); constSim.alpha = 0.6; startConstSim(); }\nfunction constLiveBump() {\n if (!constLoaded) return;\n constSim.alpha = Math.max(constSim.alpha, 0.5); startConstSim();\n if (constLiveTimer) clearTimeout(constLiveTimer);\n constLiveTimer = setTimeout(function () { constLoaded = false; loadConstellation(); }, 4000);\n}\n\n// \u2500\u2500\u2500\u2500 Intelligence analytics strip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nasync function refreshIntelAnalytics() {\n const d = await api('/api/v6/memory-intelligence');\n if (!d) return;\n animateNum('intel-total', d.totalEntries || 0);\n const cov = Math.round((d.sourceCoverage || 0) * 100);\n const covEl = document.getElementById('intel-coverage'); if (covEl) covEl.textContent = cov + '%';\n const covM = document.getElementById('intel-coverage-meter'); if (covM) covM.style.width = cov + '%';\n animateNum('intel-recent', d.recent24h || 0);\n animateNum('intel-highimp', d.highImportance || 0);\n drawIntelAgents(d.byAgent || []);\n drawIntelCats(d.byCategory || []);\n renderTagCloud(d.topTags || []);\n renderIntelRecent(d.recent || []);\n}\nfunction drawIntelAgents(rows) {\n const cv = document.getElementById('chart-intel-agents'); if (!cv || !window.Chart) return;\n const t = chartTheme(); const top = rows.slice(0, 6);\n if (charts.intelAgents) charts.intelAgents.destroy();\n charts.intelAgents = new Chart(cv, { type: 'bar', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: '#18ffff', borderRadius: 5 }] }, options: { indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { color: t.grid }, ticks: { color: t.text } }, y: { grid: { display: false }, ticks: { color: t.text } } } } });\n}\nfunction drawIntelCats(rows) {\n const cv = document.getElementById('chart-intel-cats'); if (!cv || !window.Chart) return;\n const t = chartTheme(); const top = rows.slice(0, 6);\n if (charts.intelCats) charts.intelCats.destroy();\n charts.intelCats = new Chart(cv, { type: 'doughnut', data: { labels: top.map(function (r) { return r.name; }), datasets: [{ data: top.map(function (r) { return r.count; }), backgroundColor: ['#18ffff', '#a855f7', '#ec4899', '#10e68c', '#ffb53f', '#4fb6ff'], borderWidth: 0 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: t.text, boxWidth: 12 } } } } });\n}\nfunction renderTagCloud(rows) {\n const el = document.getElementById('intel-tags'); if (!el) return;\n el.innerHTML = rows.length ? rows.slice(0, 18).map(function (r) { return '<span class=\"tc\">#' + htmlEscape(r.name) + ' <b>' + r.count + '</b></span>'; }).join('') : '<div class=\"empty-tight\">No tags yet.</div>';\n}\nfunction renderIntelRecent(rows) {\n const el = document.getElementById('intel-recent-list'); if (!el) return;\n el.innerHTML = rows.length ? rows.slice(0, 10).map(function (e) { return '<div class=\"memory-item\"><div class=\"meta\"><span class=\"pill purple\">' + htmlEscape(e.agentTool || 'agent') + '</span><span>' + htmlEscape(e.category || '') + '</span></div><div class=\"content\">' + htmlEscape(String(e.content || '').slice(0, 160)) + '</div></div>'; }).join('') : '<div class=\"empty-tight\">No memories yet.</div>';\n}\n\n// \u2500\u2500\u2500\u2500 Overview constellation hero \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction updateConstHero() {\n if (!constStats) return;\n const nodes = constStats.memories + constStats.projects + constStats.agents + constStats.tags;\n const n = document.getElementById('hero-const-nodes'); if (n) n.textContent = nodes;\n const e = document.getElementById('hero-const-edges'); if (e) e.textContent = constStats.edges;\n const m = document.getElementById('hero-const-mem'); if (m) m.textContent = constStats.memories;\n}\nasync function loadConstHeroStats() {\n if (constStats) { updateConstHero(); return; }\n const snap = await api('/api/v6/memory-graph?limit=400&maxNodes=180');\n if (snap && snap.stats) { constStats = snap.stats; updateConstHero(); }\n}\n\n// \u2500\u2500\u2500\u2500 Command Palette \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst commands = [\n ...FEATURES.map(([id, name, desc]) => ({ type: 'nav', label: name, target: id, hint: desc || 'Go to' })),\n { type: 'action', label: 'Run Reflections now', fn: 'runDream' },\n { type: 'action', label: 'Clean up memory now', fn: 'runForget' },\n { type: 'action', label: 'Toggle dark / light theme', fn: 'toggleTheme' },\n { type: 'action', label: 'Connect all tools', fn: 'attachAll' },\n { type: 'action', label: 'Refresh everything', fn: 'refresh' },\n];\nlet cmdSelected = 0;\nfunction openCmdPalette() {\n document.getElementById('cmd-overlay').classList.add('open');\n const input = document.getElementById('cmd-input');\n input.value = ''; cmdSelected = 0;\n renderCmdResults(commands);\n setTimeout(() => input.focus(), 50);\n}\nfunction closeCmdPalette() { document.getElementById('cmd-overlay').classList.remove('open'); }\nfunction renderCmdResults(items) {\n document.getElementById('cmd-results').innerHTML = items.map((c, i) => `<div class=\"cmd-result ${i === cmdSelected ? 'selected' : ''}\" onclick=\"runCmd(${i})\" data-idx=\"${i}\"><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">${c.type === 'nav' ? '<line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"/><polyline points=\"12 5 19 12 12 19\"/>' : '<circle cx=\"12\" cy=\"12\" r=\"10\"/><polygon points=\"10 8 16 12 10 16 10 8\"/>'}</svg><div class=\"meta\">${c.label}</div><span class=\"hint\">${c.hint || c.type}</span></div>`).join('');\n}\nfunction runCmd(i) {\n const filtered = filterCmds(document.getElementById('cmd-input').value);\n const c = filtered[i];\n if (!c) return;\n closeCmdPalette();\n if (c.type === 'nav') switchTab(c.target, '');\n else if (c.type === 'action' && typeof window[c.fn] === 'function') window[c.fn]();\n}\nfunction filterCmds(q) {\n if (!q) return commands;\n const l = q.toLowerCase();\n return commands.filter(c => c.label.toLowerCase().includes(l));\n}\ndocument.getElementById('cmd-input').addEventListener('input', e => { cmdSelected = 0; renderCmdResults(filterCmds(e.target.value)); });\ndocument.getElementById('cmd-input').addEventListener('keydown', e => {\n const filtered = filterCmds(e.target.value);\n if (e.key === 'ArrowDown') { e.preventDefault(); cmdSelected = Math.min(cmdSelected + 1, filtered.length - 1); renderCmdResults(filtered); }\n else if (e.key === 'ArrowUp') { e.preventDefault(); cmdSelected = Math.max(cmdSelected - 1, 0); renderCmdResults(filtered); }\n else if (e.key === 'Enter') { e.preventDefault(); runCmd(cmdSelected); }\n else if (e.key === 'Escape') closeCmdPalette();\n});\ndocument.addEventListener('keydown', e => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); openCmdPalette(); } });\n\n// \u2500\u2500\u2500\u2500 Live log / ticker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst logEntries = [];\nfunction appendLog(source, type, detail) {\n logEntries.unshift({ t: new Date(), source, type, detail: String(detail || '').slice(0, 320) });\n if (logEntries.length > 200) logEntries.length = 200;\n renderTicker(); renderLogPanel(); morphBrainState(type);\n}\nfunction renderTicker() {\n const el = document.getElementById('log-scroll');\n if (!el) return;\n const recent = logEntries.slice(0, 6).map(e => `${e.t.toLocaleTimeString()} \u00B7 ${e.source}/${e.type} \u00B7 ${e.detail.slice(0, 60)}`).join(' || ');\n el.textContent = recent || 'Awaiting activity\u2026';\n}\nfunction renderLogPanel() {\n const panel = document.getElementById('log-panel-list');\n if (!panel) return;\n panel.innerHTML = logEntries.slice(0, 80).map(e => `<div class=\"log-row\"><span class=\"t\">${htmlEscape(e.t.toLocaleTimeString())}</span><span class=\"src\">${htmlEscape(e.source)}</span><span class=\"type\">${htmlEscape(e.type)}</span><span>${htmlEscape(e.detail)}</span></div>`).join('');\n}\nfunction toggleLogPanel() { document.getElementById('log-panel').classList.toggle('open'); if (document.getElementById('log-panel').classList.contains('open')) renderLogPanel(); }\nfunction clearLogs() { logEntries.length = 0; renderTicker(); renderLogPanel(); }\nconst BRAIN_STATES = { 'memory-write':'learning \u00B7 writing memory','subagent-spawn':'sub-agent dispatched','dream':'dreaming \u00B7 reflecting','revisit':'revisiting recent','counterfactual':'running counterfactual','consolidation':'consolidating','contradiction':'resolving conflict','quarantine':'isolating suspect','collision':'arbitrating collision','firewall-block':'blocking risky action' };\nlet stateTimer = null;\nfunction morphBrainState(type) {\n const el = document.getElementById('brain-state');\n el.textContent = BRAIN_STATES[type] || 'learning \u00B7 reflecting \u00B7 charging';\n if (stateTimer) clearTimeout(stateTimer);\n stateTimer = setTimeout(() => { el.textContent = 'learning \u00B7 reflecting \u00B7 charging'; }, 6000);\n}\nasync function pullRecentActivity() {\n // Reconciliation safety net: pick up anything the WebSocket missed\n // (e.g. a dropped frame). ingestActivity dedups, so live events that\n // already arrived over the socket are ignored here.\n const log = await api('/api/v6/activity-log?limit=60');\n if (!Array.isArray(log)) return;\n for (let i = log.length - 1; i >= 0; i--) ingestActivity(log[i], false);\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n}\n\n// \u2500\u2500\u2500\u2500 WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction connectWS() {\n const proto = location.protocol === 'https:' ? 'wss' : 'ws';\n ws = new WebSocket(`${proto}://${location.host}`);\n ws.onopen = () => { appendLog('ws','connected','dashboard WebSocket online'); setConn(true); toast('Connected', 'live updates enabled', 'success'); };\n ws.onmessage = e => { try { const m = JSON.parse(e.data);\n if (m.type === 'activity' && m.event) {\n if (ingestActivity(m.event, true)) {\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n if (m.event.type === 'memory-write' && document.getElementById('tab-intelligence')?.classList.contains('active')) constLiveBump();\n }\n return;\n }\n if (m.type === 'activity-backlog' && Array.isArray(m.events)) {\n for (let i = m.events.length - 1; i >= 0; i--) ingestActivity(m.events[i], false);\n renderSparkline(); renderLivePulse();\n if (document.getElementById('tab-activity')?.classList.contains('active')) renderActivityTable();\n return;\n }\n if (m.type === 'insights' || m.type === 'modules') refresh();\n if (m.type === 'insights' && m.insights) for (const ins of m.insights.slice(0,5)) appendLog(ins.sourceAgent || 'brain','insight',ins.title || ins.content);\n if (m.type === 'injection') appendLog(m.adapter || 'agent','injection', m.insightTitle || 'context injected');\n if (m.type === 'info') appendLog('brain','info', m.message);\n if (m.type === 'error') { appendLog('brain','error', m.error); toast('Brain error', m.error, 'error'); }\n if (m.type === 'agents-detected') appendLog('brain','agents', (m.agents || []).join(', '));\n } catch {} };\n ws.onclose = () => { appendLog('ws','disconnected','reconnecting'); setConn(false); setTimeout(connectWS, 2000); };\n ws.onerror = () => { setConn(false); };\n}\n\n// \u2500\u2500\u2500\u2500 Bootstrapping \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconnectWS();\nrefresh();\npullRecentActivity();\nsetInterval(refresh, 5000); // dashboard cards heartbeat\nsetInterval(pullRecentActivity, 15000); // reconciliation only \u2014 live events arrive via WebSocket\nsetInterval(renderSparkline, 10000); // keep the events/min window current\nif (window.lucide) try { window.lucide.createIcons(); } catch {}\n\n// Welcome toast\nsetTimeout(() => toast('Welcome to Shadow Brain', 'Showing the essentials. Flip to Advanced (top-left) for every feature, or press \u2318K to search.', 'info'), 800);\n</script>\n</body>\n</html>";
|
|
2
2
|
//# sourceMappingURL=v6-dashboard-html.d.ts.map
|