CAPT-Memory-Palace / index.html
CAPT Agent
πŸ”₯ Live CAPT brain connectivity + JS health monitoring
0cc83ce
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CAPT Memory Palace - Live</title>
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
<link rel="stylesheet" href="css/styles.css">
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Space Grotesk', sans-serif;
background: #0a0a12;
color: #fff;
overflow: hidden;
}
#canvas-container {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1;
}
/* Narrative Overlay - Top Center */
#narrative-overlay {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 50;
text-align: center;
pointer-events: none;
width: 90%;
max-width: 600px;
}
#phase-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: rgba(0,0,0,0.7);
border: 1px solid rgba(139,92,246,0.4);
border-radius: 20px;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
backdrop-filter: blur(10px);
}
#phase-dot {
width: 8px; height: 8px;
border-radius: 50%;
background: #6b7280;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.5; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
#narrative-text {
font-size: 24px;
font-weight: 500;
color: #fff;
text-shadow: 0 2px 20px rgba(139,92,246,0.5);
background: rgba(0,0,0,0.6);
padding: 16px 24px;
border-radius: 16px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.1);
}
#narrative-text .highlight {
color: #8b5cf6;
font-weight: 600;
}
/* Progress Bar */
#progress-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: rgba(255,255,255,0.1);
z-index: 60;
}
#progress-bar {
height: 100%;
width: 0%;
background: linear-gradient(90deg, #8b5cf6, #ec4899);
transition: width 0.3s ease;
}
/* Query Input - Bottom Center */
#query-panel {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
z-index: 50;
display: flex;
gap: 12px;
width: 90%;
max-width: 600px;
}
#query-input {
flex: 1;
padding: 16px 20px;
background: rgba(0,0,0,0.8);
border: 1px solid rgba(139,92,246,0.4);
border-radius: 12px;
color: #fff;
font-family: 'Space Grotesk', sans-serif;
font-size: 16px;
outline: none;
backdrop-filter: blur(10px);
}
#query-input:focus {
border-color: rgba(139,92,246,0.8);
box-shadow: 0 0 20px rgba(139,92,246,0.3);
}
#query-input::placeholder {
color: #6b7280;
}
#submit-btn {
padding: 16px 24px;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
border: none;
border-radius: 12px;
color: #fff;
font-family: 'Space Grotesk', sans-serif;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
#submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(139,92,246,0.4);
}
/* Stats Panel - Top Left */
#stats-panel {
position: fixed;
top: 20px;
left: 20px;
z-index: 50;
background: rgba(0,0,0,0.7);
border: 1px solid rgba(139,92,246,0.3);
border-radius: 12px;
padding: 16px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
backdrop-filter: blur(10px);
}
.stat-row {
display: flex;
justify-content: space-between;
gap: 20px;
margin-bottom: 8px;
}
.stat-row:last-child { margin-bottom: 0; }
.stat-label { color: #6b7280; }
.stat-value { color: #8b5cf6; }
/* Control Panel - Top Right */
#control-panel {
position: fixed;
top: 20px;
right: 20px;
z-index: 50;
display: flex;
flex-direction: column;
gap: 8px;
}
.ctrl-btn {
padding: 10px 16px;
background: rgba(0,0,0,0.7);
border: 1px solid rgba(255,255,255,0.1);
color: #fff;
border-radius: 8px;
font-size: 13px;
cursor: pointer;
backdrop-filter: blur(10px);
transition: all 0.2s;
}
.ctrl-btn:hover {
background: rgba(139,92,246,0.3);
border-color: rgba(139,92,246,0.5);
}
.ctrl-btn.active {
background: rgba(139,92,246,0.4);
border-color: #8b5cf6;
}
/* Onboarding - Centered overlay */
#onboarding {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #0a0a12 0%, #1a1025 50%, #0d0d18 100%);
opacity: 1;
transition: opacity 0.8s ease-out, visibility 0.8s;
}
#onboarding.hidden {
opacity: 0;
visibility: hidden;
pointer-events: none;
}
.onboarding-content {
text-align: center;
max-width: 600px;
padding: 40px;
}
.logo {
font-size: 72px;
margin-bottom: 16px;
}
.tagline {
font-size: 32px;
font-weight: 300;
color: #e0d6eb;
margin-bottom: 16px;
line-height: 1.3;
}
.tagline-accent {
background: linear-gradient(135deg, #8b5cf6, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.description {
font-size: 16px;
color: #6b7280;
margin-bottom: 40px;
line-height: 1.6;
}
.cta-buttons {
display: flex;
gap: 16px;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 16px 32px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
font-family: inherit;
}
.btn-primary {
background: linear-gradient(135deg, #8b5cf6, #ec4899);
color: #fff;
box-shadow: 0 4px 24px rgba(139,92,246,0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(139,92,246,0.5);
}
.btn-secondary {
background: rgba(255,255,255,0.08);
color: #e0d6eb;
border: 1px solid rgba(255,255,255,0.12);
}
.btn-secondary:hover {
background: rgba(255,255,255,0.12);
}
/* Demo animation */
.demo-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 32px;
}
.demo-module {
padding: 16px;
background: rgba(255,255,255,0.03);
border-radius: 12px;
border: 1px solid rgba(139,92,246,0.2);
animation: module-pulse 2s ease-in-out infinite;
}
.demo-module:nth-child(1) { animation-delay: 0s; }
.demo-module:nth-child(2) { animation-delay: 0.3s; }
.demo-module:nth-child(3) { animation-delay: 0.6s; }
.demo-module:nth-child(4) { animation-delay: 0.9s; }
@keyframes module-pulse {
0%, 100% { border-color: rgba(139,92,246,0.2); transform: scale(1); }
50% { border-color: rgba(139,92,246,0.8); transform: scale(1.02); }
}
.demo-module-name {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
color: #8b5cf6;
}
.demo-module-status {
font-size: 11px;
color: #6b7280;
margin-top: 4px;
}
/* Keyboard hints */
.keyboard-hints {
position: fixed;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 16px;
font-size: 11px;
color: #4b5563;
}
.kbd {
padding: 4px 8px;
background: rgba(255,255,255,0.1);
border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
}
/* Message history */
#message-history {
position: fixed;
bottom: 100px;
left: 20px;
z-index: 40;
display: flex;
flex-direction: column;
gap: 8px;
max-width: 300px;
}
.history-msg {
padding: 8px 12px;
background: rgba(0,0,0,0.5);
border-radius: 8px;
font-size: 12px;
color: #6b7280;
border-left: 2px solid #8b5cf6;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.modal-content {
animation: modalIn 0.3s ease;
}
@keyframes modalIn {
from { transform: scale(0.9); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* Skip link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #8b5cf6;
color: white;
padding: 8px;
z-index: 100;
border-radius: 0 0 8px 8px;
transition: top 0.3s;
}
.skip-link:focus {
top: 0;
}
/* Focus visible outlines */
*:focus-visible {
outline: 2px solid #8b5cf6;
outline-offset: 2px;
}
/* Button focus states */
.ctrl-btn:focus-visible {
background: rgba(139, 92, 246, 0.2);
}
/* Touch-friendly minimum size */
.ctrl-btn {
min-height: 44px;
min-width: 44px;
}
/* Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
</style>
</head>
<body><a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Loading Overlay -->
<div id="loading-overlay" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #0a0a12; z-index: 9999; display: flex; align-items: center; justify-content: center; flex-direction: column; transition: opacity 0.5s;">
<div style="font-size: 48px; margin-bottom: 16px;">🧠</div>
<div style="color: #8b5cf6; font-size: 18px; font-family: 'Space Grotesk', sans-serif;">Loading Memory Palace...</div>
<div style="color: #666; font-size: 12px; margin-top: 8px;">Initializing cognitive architecture</div>
</div>
<!-- Progress Bar -->
<div id="progress-container">
<div id="progress-bar"></div>
</div>
<!-- Narrative Overlay -->
<div id="narrative-overlay" aria-live="polite" aria-atomic="true">
<div id="phase-indicator">
<span id="phase-dot"></span>
<span id="phase-name">READY</span>
</div>
<div id="narrative-text">Enter a query below to begin...</div>
</div>
<!-- Stats Panel -->
<div id="stats-panel">
<div class="stat-row">
<span class="stat-label">Sweep</span>
<span class="stat-value" id="stat-sweep">β€”</span>
</div>
<div class="stat-row">
<span class="stat-label">Active</span>
<span class="stat-value" id="stat-modules">0</span>
</div>
<div class="stat-row">
<span class="stat-label">Confidence</span>
<span class="stat-value" id="stat-confidence">β€”</span>
</div>
<div class="stat-row">
<span class="stat-label">Input</span>
<span class="stat-value" id="stat-input">β€”</span>
</div>
</div>
<!-- Control Panel - Top Right -->
<div id="control-panel">
<button class="ctrl-btn" onclick="toggleCaptPanel()" aria-label="Show/hide CAPT connection panel">πŸ”— CAPT</button>
<button class="ctrl-btn" id="btn-tour" aria-label="Toggle cinematic tour" onclick="toggleTour()">🎬 Tour</button>
<button class="ctrl-btn" id="btn-audio" aria-label="Toggle sound" onclick="toggleAudio()">πŸ”‡ Sound</button>
<button class="ctrl-btn" onclick="openJsonEditor()">πŸ“ Edit</button>
<button class="ctrl-btn" onclick="showBubbleViewer()">🫧 Modules</button>
<button class="ctrl-btn" id="btn-fullscreen" aria-label="Toggle fullscreen mode" onclick="toggleFullscreen()">β›Ά Full</button>
</div>
<div id="canvas-container"></div>
<!-- Onboarding -->
<div id="onboarding">
<div class="onboarding-content">
<div class="logo">🧠</div>
<h1 class="tagline">Watch <span class="tagline-accent">AI cognition</span> unfold in real-time</h1>
<p class="description">
See how CAPT processes your query: from pattern recognition to consensus building.
Watch modules activate, compete, and synthesize in 3D.
</p>
<div class="demo-grid">
<div class="demo-module">
<div class="demo-module-name">PULSE</div>
<div class="demo-module-status">Processing input...</div>
</div>
<div class="demo-module">
<div class="demo-module-name">NEDA</div>
<div class="demo-module-status">Finding patterns</div>
</div>
<div class="demo-module">
<div class="demo-module-name">QIPC</div>
<div class="demo-module-status">Building consensus</div>
</div>
<div class="demo-module">
<div class="demo-module-name">NDS</div>
<div class="demo-module-status">Generating output</div>
</div>
</div>
<div class="cta-buttons">
<button class="btn btn-primary" onclick="startDemo()">▢️ Watch Demo</button>
<button class="btn btn-secondary" onclick="skipOnboarding()">Skip to Editor</button>
</div>
</div>
</div>
<!-- Query Input -->
<div id="query-panel">
<select id="query-presets" onchange="loadPresetQuery()" style="padding: 16px; background: rgba(0,0,0,0.8); border: 1px solid rgba(139,92,246,0.4); border-radius: 12px; color: #8b5cf6; font-family: 'Space Grotesk', sans-serif; font-size: 14px; outline: none;">
<option value="">Try a preset...</option>
<option value="What is consciousness?">What is consciousness?</option>
<option value="Explain quantum entanglement">Explain quantum entanglement</option>
<option value="Write a poem about AI">Write a poem about AI</option>
<option value="How does memory work?">How does memory work?</option>
<option value="What is the meaning of life?">What is the meaning of life?</option>
<option value="Describe the taste of strawberries">Describe the taste of strawberries</option>
<option value="Why do we dream?">Why do we dream?</option>
</select>
<input type="text" id="query-input" placeholder="Or type your own question..." autocomplete="off">
<button id="submit-btn" onclick="submitQuery()">β†’</button>
</div>
<!-- Keyboard Hints -->
<div class="keyboard-hints">
<span><span class="kbd">Space</span> Tour</span>
<span><span class="kbd">WASD</span> Move</span>
<span><span class="kbd">M</span> Sound</span>
<span><span class="kbd">R</span> Reset</span>
</div>
<!-- Message History -->
<div id="message-history"></div>
<!-- JSON Editor Modal -->
<div id="json-editor-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 200; align-items: center; justify-content: center;">
<div style="width: 90%; max-width: 800px; max-height: 80vh; background: #12121a; border: 1px solid rgba(139,92,246,0.4); border-radius: 16px; display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.1); display: flex; justify-content: space-between; align-items: center;">
<h3 style="margin: 0; color: #8b5cf6;">Palace JSON Editor</h3>
<button onclick="closeJsonEditor()" style="background: none; border: none; color: #888; font-size: 24px; cursor: pointer;">&times;</button>
</div>
<textarea id="json-editor-textarea" style="flex: 1; background: #0a0a12; color: #e0d6eb; border: none; padding: 20px; font-family: 'JetBrains Mono', monospace; font-size: 13px; resize: none; min-height: 400px;"></textarea>
<div style="padding: 16px 20px; border-top: 1px solid rgba(255,255,255,0.1); display: flex; gap: 12px; justify-content: flex-end;">
<button onclick="closeJsonEditor()" style="padding: 10px 20px; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); color: #fff; border-radius: 8px; cursor: pointer;">Cancel</button>
<button onclick="applyJsonEditor()" style="padding: 10px 20px; background: linear-gradient(135deg, #8b5cf6, #ec4899); border: none; color: #fff; border-radius: 8px; cursor: pointer;">Apply</button>
<button onclick="downloadJson()" style="padding: 10px 20px; background: rgba(139,92,246,0.2); border: 1px solid rgba(139,92,246,0.4); color: #8b5cf6; border-radius: 8px; cursor: pointer;">Download</button>
</div>
</div>
</div>
<!-- Bubble Viewer Modal -->
<div id="bubble-viewer-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 200; align-items: center; justify-content: center;">
<div style="width: 90%; max-width: 900px; max-height: 80vh; background: #12121a; border: 1px solid rgba(139,92,246,0.4); border-radius: 16px; display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,0.1); display: flex; justify-content: space-between; align-items: center;">
<h3 style="margin: 0; color: #d946ef;">CAPT Module States</h3>
<button onclick="closeBubbleViewer()" style="background: none; border: none; color: #888; font-size: 24px; cursor: pointer;">&times;</button>
</div>
<div id="bubble-content" style="flex: 1; padding: 20px; overflow-y: auto; display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 16px;"></div>
</div>
</div>
<!-- Three.js (bundled locally for HF Spaces compatibility) -->
<script src="vendor/three.min.js"></script>
<script src="js/narrative.js"></script>
<script src="js/simulator.js"></script>
<script src="js/audio.js"></script>
<script src="js/tour.js"></script>
<script src="js/api.js"></script>
<script src="js/main.js"></script>
<script>
// Global state
let isDemoRunning = false;
let audioEnabled = false;
let currentNarrative = null;
// Global state for features
window.captActivity = 0;
window.echoMemories = [];
window.sweepEvents = [];
// Module info database
const MODULE_INFO = {
'PULSE': { name: 'Language Processing', desc: 'Tokenizes and processes raw input, breaking down queries into semantic units.', color: '#8b5cf6' },
'NEDA': { name: 'Pattern Recognition', desc: 'Matches input against learned patterns, finding relevant associations.', color: '#ec4899' },
'HMC': { name: 'Memory Encoding', desc: 'Encodes new information into the memory palace using spatial navigation.', color: '#f59e0b' },
'QIPC': { name: 'Consensus Builder', desc: 'Reconciles competing interpretations through weighted voting.', color: '#22d3ee' },
'ECHO': { name: 'Long-term Memory', desc: 'Retrieves relevant memories and synthesizes with current context.', color: '#4ade80' },
'NDS': { name: 'Output Synthesis', desc: 'Generates final coherent output from consensus pathways.', color: '#60a5fa' },
'BARK': { name: 'Security Filter', desc: 'Validates output against safety guidelines and policies.', color: '#f472b6' },
'SPAR': { name: 'Sparse Retrieval', desc: 'Fast approximate memory lookup using embedding similarity.', color: '#a78bfa' }
};
// Show module detail popup
function showModulePopup(moduleId) {
const info = MODULE_INFO[moduleId] || { name: moduleId, desc: 'Unknown module', color: '#888' };
document.getElementById('popup-title').textContent = moduleId + ' - ' + info.name;
document.getElementById('popup-title').style.color = info.color;
document.getElementById('popup-desc').textContent = info.desc;
document.getElementById('popup-status').textContent = Math.random() > 0.3 ? 'Active' : 'Idle';
document.getElementById('popup-activations').textContent = Math.floor(Math.random() * 1000);
document.getElementById('popup-sweep').textContent = '#' + Math.floor(Math.random() * 10000);
document.getElementById('module-popup').style.display = 'block';
}
function closeModulePopup() {
document.getElementById('module-popup').style.display = 'none';
}
// Theme switching
function setTheme(themeName) {
CONFIG.theme = themeName;
applyThemeColors(themeName);
repositionRooms(themeName);
console.log('[Palace] Theme:', themeName);
}
function applyThemeColors(theme) {
const colors = {
cinematic: { bg: 0x0a0a12, primary: 0x8b5cf6, secondary: 0xec4899 },
neural: { bg: 0x0f172a, primary: 0xec4899, secondary: 0x8b5cf6 },
radial: { bg: 0x071013, primary: 0x22d3ee, secondary: 0x4ade80 },
grid: { bg: 0x0a0f0a, primary: 0x4ade80, secondary: 0x22d3ee }
};
const c = colors[theme] || colors.cinematic;
if (scene) scene.background = new THREE.Color(c.bg);
}
function repositionRooms(theme) {
if (!palaceRooms || palaceRooms.length === 0) return;
const layouts = {
cinematic: [{x:0,y:0,z:0}, {x:3,y:1,z:-2}, {x:-3,y:1,z:-2}, {x:0,y:2,z:-4}, {x:0,y:-1,z:-3}, {x:2,y:0,z:-5}, {x:-2,y:0,z:-5}, {x:0,y:1,z:-6}],
neural: [{x:0,y:0,z:0}, {x:2,y:0,z:-2}, {x:-2,y:0,z:-2}, {x:1,y:2,z:-3}, {x:-1,y:2,z:-3}, {x:0,y:-2,z:-3}, {x:3,y:-1,z:-4}, {x:-3,y:-1,z:-4}],
radial: [{x:0,y:0,z:0}, {x:4,y:0,z:0}, {x:-4,y:0,z:0}, {x:0,y:4,z:-2}, {x:0,y:-4,z:-2}, {x:2.8,y:2.8,z:-3}, {x:-2.8,y:2.8,z:-3}, {x:2.8,y:-2.8,z:-3}],
grid: [{x:-3,y:0,z:0}, {x:0,y:0,z:0}, {x:3,y:0,z:0}, {x:-3,y:0,z:-3}, {x:0,y:0,z:-3}, {x:3,y:0,z:-3}, {x:-3,y:0,z:-6}, {x:0,y:0,z:-6}]
};
const layout = layouts[theme] || layouts.cinematic;
palaceRooms.forEach((room, i) => {
if (layout[i]) {
room.position.set(layout[i].x, layout[i].y, layout[i].z);
}
});
}
// JSON Editor functions
function openJsonEditor() {
const config = { schemaVersion: "3.0.0", name: "CAPT Cognitive Architecture", theme: CONFIG.theme, rooms: [] };
palaceRooms.forEach(r => {
if (r.userData && r.userData.roomId) {
config.rooms.push({ id: r.userData.roomId, position: { x: r.position.x, y: r.position.y, z: r.position.z } });
}
});
document.getElementById('json-input').value = JSON.stringify(config, null, 2);
document.getElementById('json-editor').style.display = 'block';
}
function closeJsonEditor() {
document.getElementById('json-editor').style.display = 'none';
}
function applyJsonConfig() {
try {
const config = JSON.parse(document.getElementById('json-input').value);
if (config.theme) setTheme(config.theme);
alert('Config applied! (Visual update requires restart in demo mode)');
} catch(e) {
alert('Invalid JSON: ' + e.message);
}
}
function exportJsonConfig() {
const config = document.getElementById('json-input').value;
const blob = new Blob([config], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'palace-config.json';
a.click();
}
function importJsonConfig() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.onchange = (e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = (ev) => {
document.getElementById('json-input').value = ev.target.result;
};
reader.readAsText(file);
};
input.click();
}
// Toggle panels
function toggleThemePanel() {
const panel = document.getElementById('theme-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
function toggleEchoPanel() {
const panel = document.getElementById('echo-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
if (panel.style.display === 'block') refreshEchoMemories();
}
// CAPT Connection functions
let captApiConnected = false;
function toggleCaptPanel() {
const panel = document.getElementById('capt-panel');
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
function connectToCAPT() {
const endpoint = document.getElementById('capt-endpoint').value;
const token = document.getElementById('capt-token').value;
if (!endpoint) {
document.getElementById('capt-status').innerHTML = 'Status: <span style="color:#ef4444">Please enter endpoint</span>';
return;
}
CAPT_API.configure({ endpoint, token });
CAPT_API.test().then(ok => {
if (ok) {
captApiConnected = true;
document.getElementById('capt-status').innerHTML = 'Status: <span style="color:#4ade80">Connected βœ“</span>';
// Start polling for state
CAPT_API.startPolling(2000, (state) => {
if (state && state.modules) {
window.captActivity = 0.8;
updatePalaceFromCAPT(state);
}
});
} else {
document.getElementById('capt-status').innerHTML = 'Status: <span style="color:#ef4444">Connection failed</span>';
}
});
}
function disconnectFromCAPT() {
CAPT_API.stopPolling();
captApiConnected = false;
document.getElementById('capt-status').innerHTML = 'Status: Disconnected';
}
function updatePalaceFromCAPT(state) {
// Update rooms based on CAPT state
if (state.modules && palaceRooms) {
state.modules.forEach((mod, i) => {
if (palaceRooms[i]) {
const scale = 1 + (mod.activation || 0.5) * 0.5;
palaceRooms[i].scale.setScalar(scale);
}
});
}
}
function toggleSweepTicker() {
const ticker = document.getElementById('sweep-ticker');
ticker.style.display = ticker.style.display === 'none' ? 'block' : 'none';
if (ticker.style.display === 'block') startSweepTicker();
}
// Echo memories (simulated for demo)
function refreshEchoMemories() {
const container = document.getElementById('echo-memories');
const memories = [
{ time: '2m ago', text: 'Query: "What is consciousness?" β†’ Pattern matched: philosophy_001' },
{ time: '5m ago', text: 'Memory encoded: neural_pathway_42' },
{ time: '8m ago', text: 'Consensus reached: QIPC score 0.87' },
{ time: '12m ago', text: 'Query: "Explain quantum entanglement" β†’ routed to NEDA' },
{ time: '15m ago', text: 'Security filter passed: BARK clean' }
];
container.innerHTML = memories.map(m =>
`<div style="padding: 8px; border-bottom: 1px solid rgba(139,92,246,0.2);">
<span style="color: #666; font-size: 10px;">${m.time}</span>
<div>${m.text}</div>
</div>`
).join('');
}
// Sweep ticker (simulated)
function startSweepTicker() {
const container = document.getElementById('sweep-events');
const events = [
'PULSE: Processing input tokens...',
'NEDA: Found 3 matching patterns',
'HMC: Encoding memory trace',
'QIPC: Voting on interpretations',
'ECHO: Retrieving related memories',
'NDS: Synthesizing output',
'BARK: Security check passed'
];
setInterval(() => {
const event = events[Math.floor(Math.random() * events.length)];
const time = new Date().toLocaleTimeString();
const div = document.createElement('div');
div.innerHTML = `<span style="color: #666;">${time}</span> ${event}`;
div.style.padding = '5px 0';
container.insertBefore(div, container.firstChild);
if (container.children.length > 10) container.lastChild.remove();
}, 2000);
}
// Initialize on load - main.js handles scene + config
document.addEventListener('DOMContentLoaded', () => {
// main.js will call initScene() and load config automatically
// Just set up UI event handlers here
// Set up query input
const queryInput = document.getElementById('query-input');
queryInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') submitQuery();
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && !e.target.matches('input')) {
e.preventDefault();
toggleTour();
}
if (e.key.toLowerCase() === 'm') {
toggleAudio();
}
});
});
// Start demo mode
function startDemo() {
document.getElementById('onboarding').classList.add('hidden');
isDemoRunning = true;
// Start cognitive simulator
CognitiveSimulator.start((state) => {
updateVisualization(state);
updateNarrative(state);
updateStats(state);
});
// Start audio (requires user interaction)
AudioEngine.init();
}
function skipOnboarding() {
document.getElementById('onboarding').classList.add('hidden');
initScene();
}
// Submit a query
function submitQuery() {
const input = document.getElementById('query-input');
const query = input.value.trim();
if (!query) return;
// Hide onboarding if visible
document.getElementById('onboarding').classList.add('hidden');
// Submit to simulator
CognitiveSimulator.submitQuery(query);
// Clear input
input.value = '';
// Reset preset dropdown
document.getElementById('query-presets').value = '';
}
// Load preset query
function loadPresetQuery() {
const select = document.getElementById('query-presets');
const input = document.getElementById('query-input');
const value = select.value;
if (value) {
input.value = value;
input.focus();
}
}
// Update 3D visualization from cognitive state
function updateVisualization(state) {
if (!window.loadPalace) return;
// Convert cognitive state to palace format
const config = {
schemaVersion: "3.0.0",
name: "Live Cognitive State",
rooms: state.modules.map((m, i) => ({
id: m.id,
label: Narrative.moduleNames[m.id] || m.id,
type: m.type || 'processing',
position: {
x: Math.cos(i * Math.PI * 2 / Math.max(state.modules.length, 1)) * 5,
y: m.activation * 2,
z: Math.sin(i * Math.PI * 2 / Math.max(state.modules.length, 1)) * 5
},
color: m.color || '#8b5cf6',
capacity: Math.round(m.activation * 100)
})),
connections: state.modules.slice(0, -1).map((m, i) => ({
from: m.id,
to: state.modules[i + 1]?.id,
type: 'forward'
})).filter(c => c.to)
};
window.loadPalace(config);
}
// Update narrative overlay
function updateNarrative(state) {
const narrativeState = Narrative.update(state.modules);
currentNarrative = narrativeState;
// Update phase indicator
const phaseName = document.getElementById('phase-name');
const phaseDot = document.getElementById('phase-dot');
const phaseColor = narrativeState.phase.color;
phaseName.textContent = narrativeState.phase.title;
phaseName.style.color = phaseColor;
phaseDot.style.background = phaseColor;
// Update narrative text
const narrativeText = document.getElementById('narrative-text');
narrativeText.innerHTML = narrativeState.message;
// Update progress bar
const progressBar = document.getElementById('progress-bar');
progressBar.style.width = `${narrativeState.progress}%`;
progressBar.style.background = `linear-gradient(90deg, ${phaseColor}, #ec4899)`;
// Play audio for phase changes
if (audioEnabled) {
AudioEngine.playPhaseSound(narrativeState.phase.title.toLowerCase());
// Play module sounds
state.modules.forEach(m => {
if (m.activation > 0.7) {
AudioEngine.playModuleActivation(m.id, m.activation);
}
});
// Consensus sound
if (narrativeState.phase.title === 'Consensus') {
AudioEngine.playConsensusSound();
}
}
// Update message history
addToHistory(narrativeState.message);
}
// Update stats panel
function updateStats(state) {
document.getElementById('stat-sweep').textContent = state.sweep || 'β€”';
document.getElementById('stat-modules').textContent = state.modules.length;
const avgConfidence = state.modules.reduce((sum, m) => sum + (m.confidence || 0), 0) / Math.max(state.modules.length, 1);
document.getElementById('stat-confidence').textContent = avgConfidence > 0 ? `${Math.round(avgConfidence * 100)}%` : 'β€”';
const inputPreview = state.input ? (state.input.length > 15 ? state.input.slice(0, 15) + '...' : state.input) : 'β€”';
document.getElementById('stat-input').textContent = inputPreview;
}
// Add message to history
function addToHistory(msg) {
const history = document.getElementById('message-history');
const el = document.createElement('div');
el.className = 'history-msg';
el.textContent = msg;
history.insertBefore(el, history.firstChild);
// Keep only last 5
while (history.children.length > 5) {
history.removeChild(history.lastChild);
}
}
// Toggle tour
function toggleTour() {
if (window.Tour) {
Tour.toggle(camera, { x: 0, y: 0, z: 0 });
document.getElementById('btn-tour').classList.toggle('active', Tour.isActive);
}
}
// Toggle audio
function toggleAudio() {
audioEnabled = AudioEngine.toggle();
const btn = document.getElementById('btn-audio');
btn.textContent = audioEnabled ? 'πŸ”Š Sound' : 'πŸ”‡ Sound';
btn.classList.toggle('active', audioEnabled);
}
// Toggle fullscreen
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen();
document.getElementById('btn-fullscreen').textContent = 'β›Ά Exit';
} else {
document.exitFullscreen();
document.getElementById('btn-fullscreen').textContent = 'β›Ά Full';
}
}
// Bubble viewer
function showBubbleViewer() {
const state = CognitiveSimulator.getState();
const modal = document.getElementById('bubble-viewer-modal');
const content = document.getElementById('bubble-content');
const allModules = ['PULSE', 'NEDA', 'HMC', 'CIG', 'HDR', 'QIPC', 'ECHO', 'META', 'IMMU', 'NDS'];
const activeIds = state.modules.map(m => m.id);
content.innerHTML = allModules.map(m => {
const active = state.modules.find(s => s.id === m);
const isActive = !!active;
const activation = active?.activation || 0;
const colors = {
processing: '#8b5cf6',
memory: '#d946ef',
quorum: '#ec4899',
validation: '#10b981',
output: '#06b6d4'
};
return `
<div style="background: rgba(255,255,255,0.03); border: 1px solid ${isActive ? colors[active.type] || '#8b5cf6' : '#333'}40; border-radius: 12px; padding: 16px; text-align: center;">
<div style="font-size: 24px; margin-bottom: 8px;">${isActive ? '🟣' : 'βšͺ'}</div>
<div style="color: #fff; font-weight: 600; font-family: 'JetBrains Mono', monospace;">${m}</div>
<div style="color: ${isActive ? colors[active.type] || '#8b5cf6' : '#666'}; font-size: 11px; margin-top: 4px;">
${isActive ? active.type.toUpperCase() : 'DORMANT'}
</div>
<div style="height: 4px; background: #1a1a2e; border-radius: 2px; margin-top: 8px; overflow: hidden;">
<div style="height: 100%; width: ${activation * 100}%; background: ${isActive ? colors[active.type] || '#8b5cf6' : '#333'};"></div>
</div>
<div style="color: #666; font-size: 10px; margin-top: 4px;">${Math.round(activation * 100)}%</div>
</div>
`;
}).join('');
modal.style.display = 'flex';
}
function closeBubbleViewer() {
document.getElementById('bubble-viewer-modal').style.display = 'none';
}
// JSON Editor
function openJsonEditor() {
const state = CognitiveSimulator.getState();
const config = {
schemaVersion: "3.0.0",
name: "My Research Palace",
theme: "cinematic",
rooms: state.modules.map(r => ({
id: r.id,
label: Narrative.moduleNames[r.id] || r.id,
type: r.type,
color: r.color,
capacity: r.capacity
})),
connections: []
};
document.getElementById('json-editor-textarea').value = JSON.stringify(config, null, 2);
document.getElementById('json-editor-modal').style.display = 'flex';
}
function closeJsonEditor() {
document.getElementById('json-editor-modal').style.display = 'none';
}
function applyJsonEditor() {
try {
const config = JSON.parse(document.getElementById('json-editor-textarea').value);
if (window.loadPalace) window.loadPalace(config);
closeJsonEditor();
} catch (e) {
alert('Invalid JSON: ' + e.message);
}
}
function downloadJson() {
const content = document.getElementById('json-editor-textarea').value;
const blob = new Blob([content], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'palace.json';
a.click();
URL.revokeObjectURL(url);
}
</script>
<!-- Module Detail Popup -->
<div id="module-popup" class="modal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8);">
<div class="modal-content" style="margin: 10% auto; padding: 30px; background: linear-gradient(135deg, #1a1025 0%, #0a0a12 100%); border: 1px solid rgba(139,92,246,0.4); border-radius: 16px; max-width: 500px; color: #fff;">
<span onclick="closeModulePopup()" style="float: right; font-size: 28px; font-weight: bold; color: #888; cursor: pointer;">&times;</span>
<h2 id="popup-title" style="color: #8b5cf6; margin-bottom: 10px;"></h2>
<p id="popup-desc" style="color: #aaa; line-height: 1.6;"></p>
<div id="popup-stats" style="margin-top: 20px; padding: 15px; background: rgba(0,0,0,0.4); border-radius: 8px;">
<div style="display: flex; justify-content: space-between; margin: 8px 0;">
<span style="color: #888;">Status:</span>
<span id="popup-status" style="color: #4ade80;"></span>
</div>
<div style="display: flex; justify-content: space-between; margin: 8px 0;">
<span style="color: #888;">Activations:</span>
<span id="popup-activations" style="color: #f472b6;"></span>
</div>
<div style="display: flex; justify-content: space-between; margin: 8px 0;">
<span style="color: #888;">Last Sweep:</span>
<span id="popup-sweep" style="color: #60a5fa;"></span>
</div>
</div>
</div>
</div>
<!-- Theme Selector -->
<div id="theme-panel" style="display: none; position: fixed; top: 70px; right: 20px; background: linear-gradient(135deg, #1a1025 0%, #0a0a12 100%); border: 1px solid rgba(139,92,246,0.4); border-radius: 12px; padding: 20px; z-index: 100;">
<h3 style="color: #8b5cf6; margin: 0 0 15px 0; font-size: 14px;">πŸ›οΈ Palace Theme</h3>
<button onclick="setTheme('cinematic')" style="display: block; width: 100%; padding: 10px; margin: 5px 0; background: rgba(139,92,246,0.2); border: 1px solid rgba(139,92,246,0.4); border-radius: 6px; color: #fff; cursor: pointer;">🎬 Cinematic</button>
<button onclick="setTheme('neural')" style="display: block; width: 100%; padding: 10px; margin: 5px 0; background: rgba(236,72,153,0.2); border: 1px solid rgba(236,72,153,0.4); border-radius: 6px; color: #fff; cursor: pointer;">🧠 Neural</button>
<button onclick="setTheme('radial')" style="display: block; width: 100%; padding: 10px; margin: 5px 0; background: rgba(34,211,238,0.2); border: 1px solid rgba(34,211,238,0.4); border-radius: 6px; color: #fff; cursor: pointer;">πŸŒ€ Radial</button>
<button onclick="setTheme('grid')" style="display: block; width: 100%; padding: 10px; margin: 5px 0; background: rgba(74,222,128,0.2); border: 1px solid rgba(74,222,128,0.4); border-radius: 6px; color: #fff; cursor: pointer;">πŸ”² Grid</button>
</div>
<!-- JSON Editor Modal -->
<div id="json-editor" class="modal" style="display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9);">
<div style="margin: 5% auto; padding: 20px; background: #0a0a12; border: 1px solid rgba(139,92,246,0.4); border-radius: 16px; max-width: 800px; max-height: 80vh; overflow: auto;">
<span onclick="closeJsonEditor()" style="float: right; font-size: 28px; color: #888; cursor: pointer;">&times;</span>
<h2 style="color: #8b5cf6;">πŸ“ Palace Config Editor</h2>
<textarea id="json-input" style="width: 100%; height: 400px; background: #1a1025; border: 1px solid rgba(139,92,246,0.4); border-radius: 8px; color: #fff; font-family: 'JetBrains Mono', monospace; padding: 15px; font-size: 12px;"></textarea>
<div style="margin-top: 15px;">
<button onclick="applyJsonConfig()" style="padding: 12px 24px; background: #8b5cf6; border: none; border-radius: 8px; color: #fff; cursor: pointer; font-weight: bold;">βœ… Apply</button>
<button onclick="exportJsonConfig()" style="padding: 12px 24px; background: rgba(74,222,128,0.2); border: 1px solid rgba(74,222,128,0.4); border-radius: 8px; color: #4ade80; cursor: pointer; margin-left: 10px;">πŸ’Ύ Export</button>
<button onclick="importJsonConfig()" style="padding: 12px 24px; background: rgba(34,211,238,0.2); border: 1px solid rgba(34,211,238,0.4); border-radius: 8px; color: #22d3ee; cursor: pointer; margin-left: 10px;">πŸ“‚ Import</button>
</div>
</div>
</div>
<!-- Sweep Ticker -->
<div id="sweep-ticker" style="display: none; position: fixed; bottom: 20px; left: 20px; background: linear-gradient(135deg, #1a1025 0%, #0a0a12 100%); border: 1px solid rgba(139,92,246,0.4); border-radius: 12px; padding: 15px 20px; max-width: 400px; z-index: 100;">
<div style="display: flex; align-items: center; margin-bottom: 10px;">
<span style="color: #8b5cf6; font-weight: bold;">πŸ“‘ Live Sweeps</span>
<span id="sweep-status" style="margin-left: 10px; width: 8px; height: 8px; background: #4ade80; border-radius: 50%; animation: pulse 1s infinite;"></span>
</div>
<div id="sweep-events" style="max-height: 150px; overflow-y: auto; font-size: 12px; color: #aaa;"></div>
</div>
<!-- Echo Memory Panel -->
<div id="echo-panel" style="display: none; position: fixed; top: 70px; left: 20px; background: linear-gradient(135deg, #1a1025 0%, #0a0a12 100%); border: 1px solid rgba(139,92,246,0.4); border-radius: 12px; padding: 20px; max-width: 350px; max-height: 60vh; overflow-y: auto; z-index: 100;">
<h3 style="color: #ec4899; margin: 0 0 15px 0; font-size: 14px;">πŸ’­ ECHO Memories</h3>
<div id="echo-memories" style="font-size: 12px; color: #aaa;"></div>
</div>
<!-- CAPT Connection Panel -->
<div id="capt-panel" style="display: none; position: fixed; top: 70px; right: 20px; background: linear-gradient(135deg, #1a1025 0%, #0a0a12 100%); border: 1px solid rgba(139,92,246,0.4); border-radius: 12px; padding: 20px; width: 300px; z-index: 100;">
<h3 style="color: #8b5cf6; margin: 0 0 15px 0; font-size: 14px;">πŸ”— CAPT Symbiote</h3>
<div style="margin-bottom: 15px;">
<label style="display: block; color: #888; font-size: 12px; margin-bottom: 5px;">API Endpoint</label>
<input type="text" id="capt-endpoint" placeholder="http://localhost:9877/v1" style="width: 100%; padding: 8px; background: rgba(0,0,0,0.4); border: 1px solid rgba(139,92,246,0.3); border-radius: 6px; color: #fff; font-size: 12px;">
</div>
<div style="margin-bottom: 15px;">
<label style="display: block; color: #888; font-size: 12px; margin-bottom: 5px;">API Token</label>
<input type="password" id="capt-token" placeholder="Optional" style="width: 100%; padding: 8px; background: rgba(0,0,0,0.4); border: 1px solid rgba(139,92,246,0.3); border-radius: 6px; color: #fff; font-size: 12px;">
</div>
<div style="display: flex; gap: 10px;">
<button onclick="connectToCAPT()" style="flex: 1; padding: 10px; background: #8b5cf6; border: none; border-radius: 6px; color: #fff; cursor: pointer; font-size: 12px;">πŸ”Œ Connect</button>
<button onclick="disconnectFromCAPT()" style="flex: 1; padding: 10px; background: rgba(239,68,68,0.2); border: 1px solid rgba(239,68,68,0.4); border-radius: 6px; color: #ef4444; cursor: pointer; font-size: 12px;">Disconnect</button>
</div>
<div id="capt-status" style="margin-top: 15px; padding: 10px; background: rgba(0,0,0,0.4); border-radius: 6px; font-size: 11px; color: #888;">
Status: Disconnected
</div>
</div>
<!-- Help Modal -->
<div id="help-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); backdrop-filter: blur(4px); z-index: 200; display: flex; align-items: center; justify-content: center;">
<div style="background: rgba(10,10,18,0.9); border: 1px solid rgba(139,92,246,0.4); border-radius: 16px; padding: 32px; width: 90%; max-width: 500px; color: #fff; position: relative;">
<button style="position: absolute; top: 16px; right: 16px; background: transparent; border: none; color: #8b5cf6; font-size: 20px; cursor: pointer;" onclick="document.getElementById('help-modal').style.display = 'none'" aria-label="Close help modal">&times;</button>
<h2 style="color: #8b5cf6; margin-top: 0; text-align: center;">🧠 CAPT Memory Palace Help</h2>
<div style="margin-top: 24px;">
<h3 style="color: #ec4899; margin-bottom: 12px;">🎯 Getting Started</h3>
<p>1. Click <strong>"Watch Demo"</strong> to see a simulated cognitive cycle<br>
2. Enter your own question in the input box<br>
3. Watch modules activate in real-time as CAPT processes your query</p>
<h3 style="color: #ec4899; margin: 20px 0 12px;">⌨️ Keyboard Controls</h3>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 14px;">
<div>Space β€” Toggle cinematic tour</div>
<div>WASD/QE β€” Navigate 3D space (WASD move, QE rotate)</div>
<div>M β€” Toggle sound</div>
<div>R β€” Reset camera to default position</div>
<div>F β€” Toggle fullscreen</div>
<div>H β€” Show/hide this help</div>
<div>T β€” Show/hide theme panel</div>
<div>C β€” Show/hide CAPT connection panel</div>
<div>E β€” Open JSON editor</div>
<div>? β€” Show/hide help</div>
</div>
<h3 style="color: #ec4899; margin: 20px 0 12px;">πŸ”Œ Live CAPT Connection</h3>
<p>Click the πŸ”— CAPT button to connect to a running CAPT symbiote for real cognitive data. Enter your endpoint and optional token.</p>
<h3 style="color: #ec4899; margin: 20px 0 12px;">🎨 Customization</h3>
<p>Use the πŸ“ Edit button to modify the palace configuration in JSON. You can change themes, room positions, colors, and connections.</p>
<div style="margin-top: 24px; text-align: center;">
<button onclick="document.getElementById('help-modal').style.display = 'none'" aria-label="Close help modal" style="padding: 10px 24px; background: #8b5cf6; border: none; border-radius: 6px; color: #fff; cursor: pointer; font-size: 14px;">Got it!</button>
</div>
</div>
</div>
</div>
<!-- CAPT Brain Status -->
<script>
const BRAINS = {
capt: "https://capt-brain-01.knowurknottty.workers.dev",
biocapt: "https://capt-brain-02-biocapt.knowurknottty.workers.dev",
frankencapt:"https://capt-brain-03-frankencapt.knowurknottty.workers.dev",
synthesis: "https://capt-brain-04-synthesis.knowurknottty.workers.dev",
council: "https://capt-brain-05-council.knowurknottty.workers.dev",
};
async function checkBrains() {
const results = {};
for (const [id, url] of Object.entries(BRAINS)) {
try {
const r = await fetch(url + "/health", { mode: "cors", signal: AbortSignal.timeout(5000) });
results[id] = r.ok ? "online" : "error";
} catch (e) { results[id] = "offline"; }
}
const online = Object.values(results).filter(s => s === "online").length;
const el = document.getElementById("capt-brain-status");
if (el) el.innerHTML = "CAPT Brains: " + online + "/5 online";
console.log("CAPT brains:", results);
}
checkBrains();
setInterval(checkBrains, 30000);
</script>
</body>
</html>