UltimateAI / index.html
AptlyDigital's picture
Update index.html
6008fe9 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neural AI Tutor | Interactive Learning Platform</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #0a0a0f;
color: #fff;
font-family: 'Segoe UI', system-ui, sans-serif;
overflow: hidden;
height: 100vh;
display: flex;
}
/* Main Canvas - Prominently Displayed */
#canvasContainer {
flex: 1;
position: relative;
background: linear-gradient(135deg, #0a0a0f 0%, #0f0f1a 100%);
}
canvas {
display: block;
width: 100%;
height: 100%;
}
/* Left Sidebar - Hidden Panels */
.sidebar {
width: 80px;
background: rgba(15, 15, 25, 0.9);
backdrop-filter: blur(10px);
border-right: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 10px;
z-index: 1000;
transition: width 0.3s ease;
}
.sidebar:hover,
.sidebar.expanded {
width: 380px;
}
.sidebar:hover .panel-content,
.sidebar.expanded .panel-content {
opacity: 1;
visibility: visible;
}
.panel-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 30px;
padding: 15px 0;
cursor: pointer;
border-radius: 12px;
transition: all 0.3s ease;
background: rgba(90, 108, 255, 0.1);
border: 1px solid rgba(90, 108, 255, 0.2);
}
.panel-toggle:hover {
background: rgba(90, 108, 255, 0.2);
transform: translateY(-2px);
}
.toggle-icon {
font-size: 1.5em;
color: #a0b0ff;
}
.panel-content {
width: 100%;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
overflow-y: auto;
max-height: calc(100vh - 120px);
}
.panel-section {
margin-bottom: 25px;
background: rgba(20, 20, 30, 0.7);
border-radius: 15px;
border: 1px solid rgba(255, 255, 255, 0.05);
overflow: hidden;
}
.panel-header {
padding: 15px;
background: rgba(30, 30, 45, 0.8);
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.panel-header:hover {
background: rgba(40, 40, 60, 0.8);
}
.panel-header h3 {
color: #a0b0ff;
font-size: 1em;
font-weight: 500;
display: flex;
align-items: center;
gap: 10px;
}
.panel-header i {
font-size: 1.2em;
}
.panel-body {
padding: 20px;
display: none;
}
.panel-body.active {
display: block;
}
.chevron {
transition: transform 0.3s ease;
}
.chevron.rotated {
transform: rotate(90deg);
}
/* Chat Interface - Horizontal and Centered */
.chat-overlay {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
width: 800px;
max-width: 85vw;
background: rgba(20, 20, 30, 0.85);
backdrop-filter: blur(20px);
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4);
z-index: 100;
display: flex;
flex-direction: column;
overflow: hidden;
opacity: 0;
animation: fadeIn 0.5s ease 1s forwards;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.chat-overlay.collapsed {
width: 400px;
height: 70px;
bottom: 20px;
}
.chat-overlay.collapsed .chat-header {
border-radius: 15px;
padding: 15px;
}
.chat-overlay.collapsed .chat-messages,
.chat-overlay.collapsed .chat-input-container {
display: none;
}
.chat-overlay.collapsed .chat-header {
cursor: pointer;
}
.chat-overlay.collapsed .chat-header:hover {
background: rgba(35, 35, 50, 0.9);
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
.chat-header {
padding: 20px;
background: rgba(25, 25, 40, 0.9);
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
display: flex;
align-items: center;
justify-content: space-between;
transition: all 0.3s ease;
}
.chat-title {
display: flex;
align-items: center;
gap: 15px;
}
.chat-title h2 {
color: #a0b0ff;
font-weight: 400;
font-size: 1.3em;
}
.ai-avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #5a6cff, #00ff9d);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3em;
}
.chat-subtitle {
color: #8892b0;
font-size: 0.85em;
line-height: 1.5;
}
.chat-controls {
display: flex;
gap: 10px;
align-items: center;
}
.collapse-btn {
width: 36px;
height: 36px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #a0b0ff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
.collapse-btn:hover {
background: rgba(90, 108, 255, 0.2);
transform: translateY(-2px);
}
.chat-messages {
display: flex;
flex-direction: row;
gap: 15px;
padding: 20px;
overflow-x: auto;
overflow-y: hidden;
height: 160px;
scrollbar-width: thin;
scrollbar-color: #5a6cff rgba(255, 255, 255, 0.05);
white-space: nowrap;
}
.chat-messages::-webkit-scrollbar {
height: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb {
background: #5a6cff;
border-radius: 3px;
}
.message {
min-width: 280px;
max-width: 350px;
padding: 15px;
border-radius: 15px;
line-height: 1.4;
position: relative;
animation: messageAppear 0.3s ease;
white-space: normal;
word-wrap: break-word;
display: inline-block;
vertical-align: top;
margin-right: 15px;
}
@keyframes messageAppear {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.message-user {
background: linear-gradient(135deg, #5a6cff, #7a8aff);
color: white;
}
.message-ai {
background: rgba(255, 255, 255, 0.08);
color: #e0e0ff;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.message-content {
font-size: 0.95em;
margin-bottom: 8px;
}
.message-time {
font-size: 0.7em;
opacity: 0.7;
text-align: right;
}
.message-typing {
min-width: 280px;
padding: 15px;
background: rgba(255, 255, 255, 0.05);
border-radius: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.typing-dot {
width: 8px;
height: 8px;
background: #a0b0ff;
border-radius: 50%;
animation: typing 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(1) { animation-delay: -0.32s; }
.typing-dot:nth-child(2) { animation-delay: -0.16s; }
@keyframes typing {
0%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-10px); }
}
.chat-input-container {
padding: 20px;
background: rgba(15, 15, 25, 0.9);
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.input-wrapper {
display: flex;
gap: 12px;
align-items: flex-end;
}
.chat-input {
flex: 1;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 15px 18px;
color: white;
font-family: 'Segoe UI', system-ui, sans-serif;
font-size: 0.95em;
resize: none;
min-height: 50px;
max-height: 100px;
transition: all 0.3s ease;
}
.chat-input:focus {
outline: none;
border-color: #5a6cff;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2);
}
.chat-input::placeholder {
color: rgba(255, 255, 255, 0.4);
}
.input-buttons {
display: flex;
gap: 8px;
}
.voice-input-btn,
.send-btn {
width: 50px;
height: 50px;
border-radius: 12px;
background: linear-gradient(135deg, #5a6cff, #7a8aff);
border: none;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
font-size: 1.1em;
}
.voice-input-btn:hover,
.send-btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(90, 108, 255, 0.4);
}
.voice-input-btn.listening {
background: linear-gradient(135deg, #ff5a5a, #ff7a7a);
animation: pulseListening 1.5s infinite;
}
@keyframes pulseListening {
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 90, 90, 0.7); }
70% { box-shadow: 0 0 0 10px rgba(255, 90, 90, 0); }
}
.send-btn {
background: rgba(0, 255, 157, 0.2);
border: 1px solid rgba(0, 255, 157, 0.3);
color: #00ff9d;
}
.send-btn:hover {
background: rgba(0, 255, 157, 0.3);
}
/* Control Elements in Sidebar */
.control-group {
margin-bottom: 20px;
}
.control-label {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
color: #a0b0ff;
font-size: 0.9em;
font-weight: 500;
}
.slider-container {
display: flex;
align-items: center;
gap: 15px;
}
input[type="range"] {
flex: 1;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: #5a6cff;
border-radius: 50%;
cursor: pointer;
transition: all 0.2s;
}
input[type="range"]::-webkit-slider-thumb:hover {
background: #7a8aff;
transform: scale(1.1);
}
.value-display {
min-width: 40px;
text-align: center;
font-family: 'Courier New', monospace;
color: #5a6cff;
font-weight: 500;
font-size: 0.9em;
}
.preset-buttons {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 8px;
margin-top: 15px;
}
.preset-btn {
padding: 10px;
background: rgba(90, 108, 255, 0.1);
border: 1px solid rgba(90, 108, 255, 0.3);
color: #a0b0ff;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
font-size: 0.85em;
text-align: center;
}
.preset-btn:hover {
background: rgba(90, 108, 255, 0.2);
transform: translateY(-2px);
}
.preset-btn.active {
background: rgba(90, 108, 255, 0.4);
color: white;
}
.voice-control-group {
margin-bottom: 20px;
}
.voice-select {
width: 100%;
padding: 12px 15px;
background: rgba(255, 255, 255, 0.07);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 10px;
color: white;
font-family: 'Segoe UI', system-ui, sans-serif;
font-size: 0.95em;
cursor: pointer;
transition: all 0.3s ease;
}
.voice-select:focus {
outline: none;
border-color: #5a6cff;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2);
}
.voice-select option {
background: #1a1a2a;
color: white;
padding: 10px;
}
.voice-control-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 15px;
}
.voice-btn {
padding: 12px;
background: rgba(90, 108, 255, 0.1);
border: 1px solid rgba(90, 108, 255, 0.3);
color: #a0b0ff;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 0.9em;
}
.voice-btn:hover {
background: rgba(90, 108, 255, 0.2);
transform: translateY(-2px);
}
.voice-btn.active {
background: rgba(90, 108, 255, 0.3);
color: white;
}
.voice-btn.stop {
background: rgba(255, 90, 90, 0.1);
border-color: rgba(255, 90, 90, 0.3);
color: #ff9d9d;
}
.voice-btn.stop:hover {
background: rgba(255, 90, 90, 0.2);
}
.voice-btn.stop.active {
background: rgba(255, 90, 90, 0.3);
}
.voice-preview-btn {
width: 100%;
padding: 12px;
background: rgba(0, 255, 157, 0.1);
border: 1px solid rgba(0, 255, 157, 0.3);
border-radius: 10px;
color: #00ff9d;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-top: 10px;
font-size: 0.9em;
}
.voice-preview-btn:hover {
background: rgba(0, 255, 157, 0.2);
transform: translateY(-2px);
}
.voice-status {
display: flex;
align-items: center;
gap: 8px;
margin-top: 15px;
padding: 10px;
background: rgba(0, 255, 157, 0.1);
border-radius: 10px;
font-size: 0.85em;
color: #00ff9d;
border: 1px solid rgba(0, 255, 157, 0.2);
}
/* System Status Bar */
.status-bar {
position: absolute;
top: 20px;
right: 20px;
background: rgba(20, 20, 30, 0.8);
backdrop-filter: blur(10px);
padding: 12px 20px;
border-radius: 12px;
font-size: 0.85em;
color: #8892b0;
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
gap: 10px;
}
.pulse-indicator {
width: 12px;
height: 12px;
background: #00ff9d;
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Loading and Error Screens */
.loading-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a0f;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2000;
}
.spinner {
width: 60px;
height: 60px;
border: 3px solid rgba(90, 108, 255, 0.3);
border-top-color: #5a6cff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.error-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #0a0a0f;
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 2001;
text-align: center;
padding: 20px;
}
.error-screen h2 {
color: #ff5a5a;
margin-bottom: 20px;
}
.error-screen p {
color: #8892b0;
margin-bottom: 30px;
max-width: 500px;
}
.retry-btn {
padding: 12px 30px;
background: #5a6cff;
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 1em;
transition: all 0.2s;
}
.retry-btn:hover {
background: #7a8aff;
transform: translateY(-2px);
}
/* Responsive Design */
@media (max-width: 1200px) {
.chat-overlay {
width: 700px;
}
.chat-overlay.collapsed {
width: 350px;
}
}
@media (max-width: 768px) {
body {
flex-direction: column;
}
.sidebar {
width: 100%;
height: 60px;
flex-direction: row;
padding: 10px;
}
.sidebar:hover,
.sidebar.expanded {
height: auto;
min-height: 300px;
}
.panel-content {
position: absolute;
top: 60px;
left: 0;
width: 100%;
background: rgba(15, 15, 25, 0.95);
padding: 20px;
}
.chat-overlay {
width: 95%;
max-width: 95vw;
}
.chat-overlay.collapsed {
width: 300px;
}
.chat-messages {
height: 140px;
}
.message {
min-width: 250px;
max-width: 300px;
}
}
/* Scrollbar Styling */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: #5a6cff;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #7a8aff;
}
</style>
</head>
<body>
<!-- Left Sidebar with Collapsible Panels -->
<div class="sidebar" id="sidebar">
<div class="panel-toggle" onclick="toggleSidebar()">
<i class="fas fa-bars toggle-icon"></i>
</div>
<div class="panel-content">
<!-- Visualization Controls -->
<div class="panel-section">
<div class="panel-header" onclick="togglePanel('visualizationPanel')">
<h3><i class="fas fa-brain"></i> Visualization Controls</h3>
<i class="fas fa-chevron-right chevron" id="visualizationChevron"></i>
</div>
<div class="panel-body" id="visualizationPanel">
<div class="control-group">
<div class="control-label">
<i class="fas fa-wave-square"></i>
<span>Animation Intensity</span>
</div>
<div class="slider-container">
<input type="range" id="intensity" min="0" max="100" value="50">
<span class="value-display" id="intensityValue">50</span>
</div>
</div>
<div class="control-group">
<div class="control-label">
<i class="fas fa-atom"></i>
<span>Particle Count</span>
</div>
<div class="slider-container">
<input type="range" id="particleCount" min="100" max="5000" value="2000" step="100">
<span class="value-display" id="particleCountValue">2000</span>
</div>
</div>
<div class="control-group">
<div class="control-label">
<i class="fas fa-bolt"></i>
<span>Energy Level</span>
</div>
<div class="slider-container">
<input type="range" id="energy" min="0" max="100" value="30">
<span class="value-display" id="energyValue">30</span>
</div>
</div>
<div class="preset-buttons">
<button class="preset-btn" data-preset="listening">🎤 Listening</button>
<button class="preset-btn" data-preset="processing">⚡ Processing</button>
<button class="preset-btn" data-preset="responding">💬 Responding</button>
<button class="preset-btn" data-preset="exploring">🔍 Exploring</button>
<button class="preset-btn" data-preset="teaching">📚 Teaching</button>
<button class="preset-btn" data-preset="idle">🌀 Idle</button>
</div>
</div>
</div>
<!-- Voice Controls -->
<div class="panel-section">
<div class="panel-header" onclick="togglePanel('voicePanel')">
<h3><i class="fas fa-robot"></i> Voice Synthesis</h3>
<i class="fas fa-chevron-right chevron" id="voiceChevron"></i>
</div>
<div class="panel-body" id="voicePanel">
<div class="voice-control-group">
<div class="control-label">
<i class="fas fa-user-circle"></i>
<span>Voice Selection</span>
</div>
<select class="voice-select" id="voiceSelect">
<option value="">Loading voices...</option>
</select>
</div>
<div class="voice-control-group">
<div class="control-label">
<i class="fas fa-tachometer-alt"></i>
<span>Speech Rate</span>
</div>
<div class="slider-container">
<input type="range" class="voice-slider" id="rateSlider" min="0.5" max="2" step="0.1" value="1">
<span class="value-display" id="rateValue">1.0x</span>
</div>
</div>
<div class="voice-control-group">
<div class="control-label">
<i class="fas fa-wave-square"></i>
<span>Pitch Variation</span>
</div>
<div class="slider-container">
<input type="range" class="voice-slider" id="pitchSlider" min="0.5" max="2" step="0.1" value="1">
<span class="value-display" id="pitchValue">1.0</span>
</div>
</div>
<div class="voice-control-group">
<div class="control-label">
<i class="fas fa-volume-up"></i>
<span>Volume Level</span>
</div>
<div class="slider-container">
<input type="range" class="voice-slider" id="volumeSlider" min="0.1" max="1" step="0.1" value="0.8">
<span class="value-display" id="volumeValue">80%</span>
</div>
</div>
<div class="voice-control-buttons">
<button class="voice-btn" id="autoSpeakBtn" data-enabled="true">
<i class="fas fa-bullhorn"></i>
Auto-Speak
</button>
<button class="voice-btn stop" id="stopBtn">
<i class="fas fa-stop"></i>
Stop
</button>
</div>
<button class="voice-preview-btn" id="previewBtn">
<i class="fas fa-play"></i>
Preview Voice (Little Red Riding Hood)
</button>
<div class="voice-status" id="voiceStatus" style="display: none;">
<i class="fas fa-comment-dots"></i>
<span id="statusMessage">Speaking...</span>
</div>
</div>
</div>
<!-- System Info -->
<div class="panel-section">
<div class="panel-header" onclick="togglePanel('systemPanel')">
<h3><i class="fas fa-info-circle"></i> System Info</h3>
<i class="fas fa-chevron-right chevron" id="systemChevron"></i>
</div>
<div class="panel-body" id="systemPanel">
<div class="control-group">
<div class="control-label">
<i class="fas fa-cube"></i>
<span>3D Renderer</span>
</div>
<div style="color: #8892b0; font-size: 0.9em; padding: 10px 0;">
Three.js v128<br>
WebGL 2.0 Enabled
</div>
</div>
<div class="control-group">
<div class="control-label">
<i class="fas fa-microchip"></i>
<span>Performance</span>
</div>
<div style="color: #8892b0; font-size: 0.9em; padding: 10px 0;">
<div id="fpsCounter">FPS: 60</div>
<div id="particleCounter">Particles: 2000</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Visualization Canvas -->
<div id="canvasContainer">
<canvas id="mainCanvas"></canvas>
<!-- Status Bar -->
<div class="status-bar">
<span class="pulse-indicator"></span>
<span id="statusText">Neural AI Tutor Ready</span>
</div>
<!-- Centered Horizontal Chat Interface -->
<div class="chat-overlay" id="chatOverlay">
<div class="chat-header">
<div class="chat-title">
<div class="ai-avatar">
<i class="fas fa-brain"></i>
</div>
<div>
<h2>Neural AI Tutor</h2>
<div class="chat-subtitle">
Interactive learning assistant with neural visualization
</div>
</div>
</div>
<div class="chat-controls">
<button class="collapse-btn" id="collapseBtn" title="Collapse chat">
<i class="fas fa-chevron-down"></i>
</button>
</div>
</div>
<div class="chat-messages" id="chatMessages">
<div class="message message-ai">
<div class="message-content">
Welcome to the Neural AI Tutor! I'm your interactive learning assistant.
I can help explain complex concepts, answer questions, and guide your learning journey.
</div>
<div class="message-time">Just now</div>
</div>
<div class="message message-ai">
<div class="message-content">
You can type your questions or use voice input. The neural visualization
responds to our conversation, showing how knowledge connects in real-time.
</div>
<div class="message-time">Just now</div>
</div>
<div class="message message-ai">
<div class="message-content">
Try asking me about quantum computing, neural networks, blockchain,
calculus, or any other subject you're curious about!
</div>
<div class="message-time">Just now</div>
</div>
<!-- Typing indicator (hidden by default) -->
<div class="message-typing" id="typingIndicator" style="display: none;">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<span style="color: #a0b0ff; font-size: 0.9em; margin-left: 10px;">Thinking...</span>
</div>
</div>
<div class="chat-input-container">
<div class="input-wrapper">
<textarea
class="chat-input"
id="chatInput"
placeholder="Ask me anything about science, math, technology, or any subject..."
rows="1"
></textarea>
<div class="input-buttons">
<button class="voice-input-btn" id="voiceBtn" title="Start voice input">
<i class="fas fa-microphone"></i>
</button>
<button class="send-btn" id="sendBtn" title="Send message">
<i class="fas fa-paper-plane"></i>
</button>
</div>
</div>
</div>
</div>
<!-- Loading Screen -->
<div class="loading-screen" id="loadingScreen">
<div class="spinner"></div>
<div id="loadingText">Initializing Neural AI Tutor...</div>
</div>
<!-- Error Screen -->
<div class="error-screen" id="errorScreen">
<h2>⚠️ System Initialization Failed</h2>
<p id="errorMessage">Unable to load 3D visualization engine. Please check your browser compatibility or try again.</p>
<button class="retry-btn" id="retryBtn">Retry Initialization</button>
</div>
</div>
<script>
// Sidebar and Panel Management
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('expanded');
}
function togglePanel(panelId) {
const panel = document.getElementById(panelId);
const chevron = document.getElementById(panelId.replace('Panel', 'Chevron'));
panel.classList.toggle('active');
chevron.classList.toggle('rotated');
}
// Chat Collapse Functionality
let isChatCollapsed = false;
const chatOverlay = document.getElementById('chatOverlay');
const collapseBtn = document.getElementById('collapseBtn');
collapseBtn.addEventListener('click', function(e) {
e.stopPropagation();
toggleChat();
});
// Click on collapsed chat header to expand
chatOverlay.addEventListener('click', function(e) {
if (isChatCollapsed && e.target.closest('.chat-header')) {
toggleChat();
}
});
function toggleChat() {
isChatCollapsed = !isChatCollapsed;
chatOverlay.classList.toggle('collapsed');
const icon = collapseBtn.querySelector('i');
if (isChatCollapsed) {
icon.className = 'fas fa-chevron-up';
collapseBtn.title = 'Expand chat';
} else {
icon.className = 'fas fa-chevron-down';
collapseBtn.title = 'Collapse chat';
}
}
// Check if Three.js loaded properly
function checkThreeJS() {
if (typeof THREE === 'undefined') {
console.error('Three.js failed to load');
showError('Three.js library failed to load. Please check your internet connection.');
return false;
}
return true;
}
function showError(message) {
const errorScreen = document.getElementById('errorScreen');
const errorMessage = document.getElementById('errorMessage');
const loadingScreen = document.getElementById('loadingScreen');
loadingScreen.style.display = 'none';
errorMessage.textContent = message;
errorScreen.style.display = 'flex';
}
function hideLoading() {
const loadingScreen = document.getElementById('loadingScreen');
loadingScreen.style.opacity = '0';
setTimeout(() => {
loadingScreen.style.display = 'none';
}, 500);
}
function updateStatus(text) {
document.getElementById('statusText').textContent = text;
document.getElementById('loadingText').textContent = text;
}
// Voice Synthesis System (unchanged from previous version)
class VoiceSynthesis {
constructor() {
this.synth = window.speechSynthesis;
this.voices = [];
this.currentVoice = null;
this.isAutoSpeak = true;
this.isSpeaking = false;
this.currentUtterance = null;
this.settings = {
rate: 1.0,
pitch: 1.0,
volume: 0.8
};
// Little Red Riding Hood story for voice preview
this.testScript = "Once upon a time, there was a little girl who lived in a village near the forest. " +
"Whenever she went out, the little girl wore a red riding cloak, so everyone in the village called her Little Red Riding Hood. " +
"One morning, Little Red Riding Hood asked her mother if she could go to visit her grandmother as it had been awhile since they'd seen each other. " +
"That's a wonderful idea, her mother said. So they packed a nice basket for Little Red Riding Hood to take to her grandmother. " +
"When the basket was ready, the little girl put on her red cloak and kissed her mother goodbye. " +
"Remember, go straight to Grandma's house, her mother cautioned. Don't dawdle along the way and please don't talk to strangers! " +
"The woods are dangerous. I will be careful, Little Red Riding Hood promised her mother. " +
"The grandmother lived out in the woods, a half hour from the village. When Little Red Riding Hood entered the woods a wolf came up to her. " +
"She did not know what a wicked animal he was, and was not afraid of him. Good day, Little Red Riding Hood, said the wolf. " +
"Thank you kindly, wolf, she replied. Where are you going so early, Little Red Riding Hood? " +
"To my grandmother's house. What do you have there in your basket? " +
"Cake and wine. Yesterday was baking day, so poor sick grandmother is to have something good, to make her stronger. " +
"Where does your grandmother live, Little Red Riding Hood? A good quarter of an hour farther in the woods. " +
"Her house is under the three large oak trees. You can tell by the nut trees which are below. " +
"The wolf thought to himself, What a tender young creature! What a nice plump mouthful! She will be better to eat than the old woman. " +
"I must act craftily, so as to catch both. So he walked for a short time by Little Red Riding Hood's side, and then he said, " +
"See, Little Red Riding Hood, how pretty the flowers are about here! Why do you not look around? " +
"I believe, too, that you do not hear how sweetly the little birds are singing. " +
"You walk gravely along as if you were going to school, while everything else out here in the wood is merry. " +
"Little Red Riding Hood raised her eyes, and when she saw the sunbeams dancing here and there through the trees, and pretty flowers growing everywhere, " +
"she thought, Suppose I take grandmother a fresh nosegay. That would please her too. It is so early in the day that I shall still get there in good time. " +
"And so she ran from the path into the woods to look for flowers. And whenever she had picked one, she fancied that she saw a still prettier one farther on, " +
"and ran after it, and so got deeper and deeper into the woods.";
this.initVoices();
this.setupEventListeners();
}
initVoices() {
// Wait for voices to be loaded
const loadVoices = () => {
this.voices = this.synth.getVoices();
this.populateVoiceList();
// Select a high-quality natural-sounding English voice
this.selectBestVoice();
};
if (this.synth.onvoiceschanged !== undefined) {
this.synth.onvoiceschanged = loadVoices;
}
// Load immediately if voices are already available
if (this.synth.getVoices().length > 0) {
loadVoices();
}
}
selectBestVoice() {
// Filter for English voices only
const englishVoices = this.voices.filter(voice =>
voice.lang.startsWith('en-')
);
if (englishVoices.length === 0) {
console.warn('No English voices found');
return;
}
// Prioritize natural-sounding voices
const preferredVoices = [
'Google UK English Male',
'Google US English',
'Microsoft David - English (United States)',
'Microsoft Zira - English (United States)',
'Alex',
'Samantha',
'Daniel',
'Karen',
'Moira',
'Tessa'
];
// Try to find a preferred voice
for (const voiceName of preferredVoices) {
const voice = englishVoices.find(v => v.name.includes(voiceName));
if (voice) {
this.currentVoice = voice;
this.updateVoiceSelect(voice);
this.showStatus(`Selected: ${voice.name}`, 3000);
break;
}
}
// Fallback to first English voice
if (!this.currentVoice && englishVoices.length > 0) {
this.currentVoice = englishVoices[0];
this.updateVoiceSelect(englishVoices[0]);
this.showStatus(`Selected: ${englishVoices[0].name}`, 3000);
}
}
populateVoiceList() {
const voiceSelect = document.getElementById('voiceSelect');
voiceSelect.innerHTML = '';
// Filter for English voices only
const englishVoices = this.voices.filter(voice =>
voice.lang.startsWith('en-')
);
if (englishVoices.length === 0) {
const option = document.createElement('option');
option.textContent = 'No English voices found';
option.disabled = true;
voiceSelect.appendChild(option);
return;
}
// Sort English voices by dialect preference
const sortedVoices = englishVoices.sort((a, b) => {
const langOrder = ['en-US', 'en-GB', 'en-AU', 'en-CA', 'en-IN'];
const indexA = langOrder.indexOf(a.lang);
const indexB = langOrder.indexOf(b.lang);
if (indexA !== -1 && indexB !== -1) return indexA - indexB;
if (indexA !== -1) return -1;
if (indexB !== -1) return 1;
return a.name.localeCompare(b.name);
});
// Add voices to select
sortedVoices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.name;
option.textContent = `${voice.name} (${this.getDialectName(voice.lang)})`;
option.dataset.lang = voice.lang;
voiceSelect.appendChild(option);
});
}
getDialectName(langCode) {
const dialects = {
'en-US': 'US English',
'en-GB': 'UK English',
'en-AU': 'Australian English',
'en-CA': 'Canadian English',
'en-IN': 'Indian English',
'en-IE': 'Irish English',
'en-ZA': 'South African English',
'en-NZ': 'New Zealand English'
};
return dialects[langCode] || langCode;
}
updateVoiceSelect(voice) {
const voiceSelect = document.getElementById('voiceSelect');
const options = voiceSelect.querySelectorAll('option');
options.forEach(option => {
if (option.value === voice.name) {
option.selected = true;
}
});
}
setupEventListeners() {
// Voice selection
document.getElementById('voiceSelect').addEventListener('change', (e) => {
const selectedVoice = this.voices.find(v => v.name === e.target.value);
if (selectedVoice) {
this.currentVoice = selectedVoice;
this.showStatus(`Voice changed to: ${selectedVoice.name}`, 2000);
}
});
// Rate slider
document.getElementById('rateSlider').addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
this.settings.rate = value;
document.getElementById('rateValue').textContent = value.toFixed(1) + 'x';
});
// Pitch slider
document.getElementById('pitchSlider').addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
this.settings.pitch = value;
document.getElementById('pitchValue').textContent = value.toFixed(1);
});
// Volume slider
document.getElementById('volumeSlider').addEventListener('input', (e) => {
const value = parseFloat(e.target.value);
this.settings.volume = value;
document.getElementById('volumeValue').textContent = Math.round(value * 100) + '%';
});
// Auto-speak toggle
document.getElementById('autoSpeakBtn').addEventListener('click', (e) => {
this.isAutoSpeak = !this.isAutoSpeak;
const btn = e.target.closest('.voice-btn');
btn.dataset.enabled = this.isAutoSpeak;
btn.innerHTML = this.isAutoSpeak
? '<i class="fas fa-bullhorn"></i> Auto-Speak'
: '<i class="fas fa-volume-mute"></i> Auto-Speak';
btn.classList.toggle('active', this.isAutoSpeak);
// Show status message
this.showStatus(this.isAutoSpeak ? 'Auto-speak enabled' : 'Auto-speak disabled', 2000);
});
// Stop button - immediately stops all speech
document.getElementById('stopBtn').addEventListener('click', () => {
this.stopAllSpeech();
});
// Preview button - reads Little Red Riding Hood
document.getElementById('previewBtn').addEventListener('click', () => {
this.previewCurrentVoice();
});
// Update status displays when speaking
this.synth.onstart = () => {
this.isSpeaking = true;
document.getElementById('stopBtn').classList.add('active');
this.showStatus('Speaking...');
// Update 3D visualization
if (window.visualization) {
window.visualization.setPreset('teaching');
}
};
this.synth.onend = () => {
this.isSpeaking = false;
document.getElementById('stopBtn').classList.remove('active');
this.hideStatus();
// Update 3D visualization
if (window.visualization) {
setTimeout(() => {
window.visualization.setPreset('idle');
}, 1000);
}
};
}
speak(text) {
if (!this.isAutoSpeak || !text || this.isSpeaking) return;
this.stopAllSpeech();
const utterance = new SpeechSynthesisUtterance(text);
if (this.currentVoice) {
utterance.voice = this.currentVoice;
}
utterance.rate = this.settings.rate;
utterance.pitch = this.settings.pitch;
utterance.volume = this.settings.volume;
// Event listeners for this utterance
utterance.onerror = (event) => {
console.error('Speech synthesis error:', event);
this.hideStatus();
};
this.currentUtterance = utterance;
this.synth.speak(utterance);
}
// Stops ALL speech immediately
stopAllSpeech() {
if (this.synth.speaking) {
this.synth.cancel(); // This stops ALL utterances immediately
this.isSpeaking = false;
this.currentUtterance = null;
document.getElementById('stopBtn').classList.remove('active');
this.hideStatus();
}
}
previewCurrentVoice() {
this.stopAllSpeech();
this.speak(this.testScript);
this.showStatus('Reading: Little Red Riding Hood', 3000);
}
showStatus(message, duration = null) {
const statusEl = document.getElementById('voiceStatus');
const messageEl = document.getElementById('statusMessage');
messageEl.textContent = message;
statusEl.style.display = 'flex';
if (duration) {
setTimeout(() => {
this.hideStatus();
}, duration);
}
}
hideStatus() {
const statusEl = document.getElementById('voiceStatus');
statusEl.style.display = 'none';
}
updateSettingsFromUI() {
this.settings.rate = parseFloat(document.getElementById('rateSlider').value);
this.settings.pitch = parseFloat(document.getElementById('pitchSlider').value);
this.settings.volume = parseFloat(document.getElementById('volumeSlider').value);
}
}
// Chat Interface Functionality (updated for horizontal layout)
class ChatInterface {
constructor() {
this.isListening = false;
this.initChat();
}
initChat() {
this.chatInput = document.getElementById('chatInput');
this.chatMessages = document.getElementById('chatMessages');
this.voiceBtn = document.getElementById('voiceBtn');
this.sendBtn = document.getElementById('sendBtn');
this.typingIndicator = document.getElementById('typingIndicator');
this.setupEventListeners();
this.autoResizeTextarea();
}
setupEventListeners() {
// Send button click
this.sendBtn.addEventListener('click', () => this.sendMessage());
// Enter key to send (Shift+Enter for new line)
this.chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
// Voice button click
this.voiceBtn.addEventListener('click', () => this.toggleVoiceInput());
// Auto-resize textarea
this.chatInput.addEventListener('input', () => this.autoResizeTextarea());
}
autoResizeTextarea() {
const textarea = this.chatInput;
textarea.style.height = 'auto';
const newHeight = Math.min(textarea.scrollHeight, 100);
textarea.style.height = newHeight + 'px';
}
toggleVoiceInput() {
this.isListening = !this.isListening;
if (this.isListening) {
// Start "listening" state
this.voiceBtn.classList.add('listening');
this.voiceBtn.innerHTML = '<i class="fas fa-stop"></i>';
// Update 3D visualization to listening mode
if (window.visualization) {
window.visualization.setPreset('listening');
}
// Simulate voice input (in a real app, this would capture actual audio)
setTimeout(() => {
this.simulateVoiceInput();
}, 1500);
} else {
// Stop "listening" state
this.voiceBtn.classList.remove('listening');
this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
// Update 3D visualization to processing mode
if (window.visualization) {
window.visualization.setPreset('processing');
}
}
}
simulateVoiceInput() {
if (!this.isListening) return;
// Simulate capturing voice input
const simulatedQuestions = [
"Can you explain quantum computing in simple terms?",
"How do neural networks actually learn from data?",
"What's the difference between artificial intelligence and machine learning?",
"Could you help me understand how blockchain technology works?",
"Explain the concept of derivatives in calculus for beginners"
];
const randomQuestion = simulatedQuestions[Math.floor(Math.random() * simulatedQuestions.length)];
// Add user message
this.addMessage(randomQuestion, 'user');
// Process and respond
this.processAIResponse(randomQuestion);
// Stop listening after receiving input
setTimeout(() => {
this.isListening = false;
this.voiceBtn.classList.remove('listening');
this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>';
}, 500);
}
sendMessage() {
const message = this.chatInput.value.trim();
if (message === '') return;
// Add user message
this.addMessage(message, 'user');
// Clear input
this.chatInput.value = '';
this.autoResizeTextarea();
// Process and respond
this.processAIResponse(message);
}
addMessage(content, sender) {
const messageDiv = document.createElement('div');
messageDiv.className = `message message-${sender}`;
const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
messageDiv.innerHTML = `
<div class="message-content">${this.escapeHtml(content)}</div>
<div class="message-time">${time}</div>
`;
this.chatMessages.appendChild(messageDiv);
// Scroll to right to show latest message
this.chatMessages.scrollLeft = this.chatMessages.scrollWidth;
// Update 3D visualization
if (window.visualization) {
if (sender === 'user') {
window.visualization.setPreset('processing');
}
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
processAIResponse(userMessage) {
// Show typing indicator
this.showTypingIndicator();
// Update 3D visualization
if (window.visualization) {
window.visualization.setPreset('processing');
}
// Simulate AI processing time
const processingTime = 1000 + Math.random() * 2000;
setTimeout(() => {
this.hideTypingIndicator();
// Generate AI response
const response = this.generateAIResponse(userMessage);
// Add AI response
this.addMessage(response, 'ai');
// Speak the response if voice synthesis is available
if (window.voiceSynthesis && window.voiceSynthesis.isAutoSpeak) {
setTimeout(() => {
window.voiceSynthesis.speak(response);
}, 300);
}
// Update 3D visualization
if (window.visualization) {
window.visualization.setPreset('responding');
}
// Return to idle after a delay
setTimeout(() => {
if (window.visualization) {
window.visualization.setPreset('idle');
}
}, 3000);
}, processingTime);
}
showTypingIndicator() {
this.typingIndicator.style.display = 'flex';
this.chatMessages.scrollLeft = this.chatMessages.scrollWidth;
}
hideTypingIndicator() {
this.typingIndicator.style.display = 'none';
}
generateAIResponse(userMessage) {
// Enhanced AI response generator with educational content
const responses = {
quantum: "Quantum computing uses quantum bits or 'qubits' that can exist in multiple states simultaneously (superposition). This allows quantum computers to process vast amounts of data in parallel. Unlike classical bits (0 or 1), qubits can be 0, 1, or both at the same time.",
neural: "Neural networks learn through backpropagation with gradient descent. They consist of layers of interconnected 'neurons' that process information. During training, the network adjusts connection weights to minimize prediction errors.",
ai: "Artificial Intelligence (AI) is the broader concept of machines being able to carry out tasks in a way we would consider 'smart'. Machine Learning (ML) is a subset of AI where machines learn from data without explicit programming.",
blockchain: "Blockchain is a distributed, decentralized digital ledger that records transactions across many computers. Each 'block' contains a cryptographic hash of the previous block, creating an immutable chain.",
calculus: "Derivatives measure instantaneous rate of change - essentially the slope of a curve at a specific point. If f(x) is your function, the derivative f'(x) tells you how fast f(x) is changing at any x.",
default: "I understand you're asking about " + userMessage.substring(0, 40) + "... This is an interesting topic! Let me provide a clear explanation with key concepts and real-world applications."
};
const lowerMessage = userMessage.toLowerCase();
if (lowerMessage.includes('quantum')) return responses.quantum;
if (lowerMessage.includes('neural') || lowerMessage.includes('network')) return responses.neural;
if (lowerMessage.includes('artificial intelligence') || lowerMessage.includes(' ai ') || lowerMessage.includes('machine learning')) return responses.ai;
if (lowerMessage.includes('blockchain')) return responses.blockchain;
if (lowerMessage.includes('calculus') || lowerMessage.includes('derivative') || lowerMessage.includes('differentiation')) return responses.calculus;
return responses.default;
}
}
// Main Visualization Class (unchanged from previous version)
class AITutorVisualization {
constructor() {
if (!checkThreeJS()) return;
this.scene = null;
this.camera = null;
this.renderer = null;
this.particles = null;
this.controls = null;
this.params = {
intensity: 0.5,
particleCount: 2000,
energy: 0.3,
mode: 'idle'
};
this.audioData = new Array(32).fill(0);
this.time = 0;
this.animationFrameId = null;
try {
this.init();
this.createParticles();
this.setupControls();
this.animate();
updateStatus('Neural AI System Ready');
setTimeout(hideLoading, 500);
} catch (error) {
console.error('Initialization error:', error);
showError('Failed to initialize 3D visualization: ' + error.message);
}
}
init() {
updateStatus('Initializing neural network visualization...');
// Scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x0a0a0f);
// Camera
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.camera.position.z = 5;
// Renderer
const canvas = document.getElementById('mainCanvas');
this.renderer = new THREE.WebGLRenderer({
canvas: canvas,
antialias: true,
alpha: true
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// Add fog
this.scene.fog = new THREE.Fog(0x0a0a0f, 10, 25);
// Add lights
const ambientLight = new THREE.AmbientLight(0x222244, 0.5);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0x5a6cff, 1);
directionalLight.position.set(5, 3, 5);
this.scene.add(directionalLight);
// Orbit controls
updateStatus('Configuring interaction controls...');
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.05;
}
createParticles() {
updateStatus('Generating neural particles...');
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(this.params.particleCount * 3);
const colors = new Float32Array(this.params.particleCount * 3);
for (let i = 0; i < this.params.particleCount; i++) {
const i3 = i * 3;
// Fibonacci sphere distribution
const phi = Math.acos(-1 + (2 * i) / this.params.particleCount);
const theta = Math.sqrt(this.params.particleCount * Math.PI) * phi;
const x = Math.cos(theta) * Math.sin(phi);
const y = Math.sin(theta) * Math.sin(phi);
const z = Math.cos(phi);
positions[i3] = x;
positions[i3 + 1] = y;
positions[i3 + 2] = z;
// Color gradient based on position
colors[i3] = 0.5 + x * 0.5;
colors[i3 + 1] = 0.3 + y * 0.7;
colors[i3 + 2] = 0.8 + z * 0.2;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const material = new THREE.PointsMaterial({
size: 0.03,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
this.particles = new THREE.Points(geometry, material);
this.scene.add(this.particles);
// Add central neural core
const coreGeometry = new THREE.SphereGeometry(0.5, 32, 32);
const coreMaterial = new THREE.MeshBasicMaterial({
color: 0x5a6cff,
wireframe: true,
transparent: true,
opacity: 0.1
});
const core = new THREE.Mesh(coreGeometry, coreMaterial);
this.scene.add(core);
}
setupControls() {
// Slider controls
const intensitySlider = document.getElementById('intensity');
const particleCountSlider = document.getElementById('particleCount');
const energySlider = document.getElementById('energy');
const intensityValue = document.getElementById('intensityValue');
const particleCountValue = document.getElementById('particleCountValue');
const energyValue = document.getElementById('energyValue');
intensitySlider.addEventListener('input', (e) => {
this.params.intensity = e.target.value / 100;
intensityValue.textContent = e.target.value;
});
particleCountSlider.addEventListener('input', (e) => {
this.params.particleCount = parseInt(e.target.value);
particleCountValue.textContent = e.target.value;
this.updateParticleCount();
});
energySlider.addEventListener('input', (e) => {
this.params.energy = e.target.value / 100;
energyValue.textContent = e.target.value;
});
// Preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const preset = e.target.dataset.preset;
this.setPreset(preset);
});
});
// Retry button
document.getElementById('retryBtn').addEventListener('click', () => {
location.reload();
});
}
setPreset(preset) {
this.params.mode = preset;
// Update UI to show active preset
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.preset === preset);
});
// Update status
const statusMap = {
'listening': '🎤 Listening for input...',
'processing': '⚡ Processing thoughts...',
'responding': '💬 Formulating response...',
'exploring': '🔍 Exploring concepts...',
'teaching': '📚 Teaching mode active',
'idle': '🌀 Neural system idle'
};
if (statusMap[preset]) {
updateStatus(statusMap[preset]);
}
}
updateParticleCount() {
if (this.particles) {
this.scene.remove(this.particles);
}
this.createParticles();
}
simulateAudioData() {
// Simulate audio data for visualization
const time = performance.now() * 0.001;
const frequency = 0.5 + this.params.energy * 2;
for (let i = 0; i < this.audioData.length; i++) {
const base = Math.sin(time * frequency + i * 0.3) * 0.5 + 0.5;
const variation = Math.sin(time * 2 + i * 0.5) * 0.2;
this.audioData[i] = base + variation * this.params.intensity;
}
}
updateParticles() {
if (!this.particles) return;
const positions = this.particles.geometry.attributes.position.array;
const originalPositions = this.particles.geometry.attributes.originalPosition;
const colors = this.particles.geometry.attributes.color.array;
// Store original positions if not already stored
if (!originalPositions) {
const origPos = new Float32Array(positions.length);
origPos.set(positions);
this.particles.geometry.setAttribute('originalPosition', new THREE.BufferAttribute(origPos, 3));
}
const origPositions = this.particles.geometry.attributes.originalPosition.array;
const time = this.time;
for (let i = 0; i < this.params.particleCount; i++) {
const i3 = i * 3;
const x = origPositions[i3];
const y = origPositions[i3 + 1];
const z = origPositions[i3 + 2];
let radius = 2.0;
let intensity = this.params.intensity;
// Apply different behaviors based on mode
switch(this.params.mode) {
case 'listening':
radius += Math.sin(time * 3 + i * 0.01) * 0.5 * intensity;
break;
case 'processing':
radius += Math.sin(time * 5 + i * 0.02) * 0.6 * intensity;
break;
case 'responding':
radius += (Math.sin(time * 2 + i * 0.005) + 1) * 0.4 * intensity;
break;
case 'exploring':
radius += Math.sin(time * 1.5 + i * 0.015) * 0.7 * intensity;
break;
case 'teaching':
radius += Math.sin(time * 4 + i * 0.008) * 0.55 * intensity;
break;
default: // idle
radius += Math.sin(time * 0.5 + i * 0.01) * 0.2 * intensity;
}
// Add energy-based pulsation
radius += Math.sin(time * 8) * 0.2 * this.params.energy;
// Add audio data influence
const audioIndex = Math.floor((i / this.params.particleCount) * this.audioData.length);
radius += this.audioData[audioIndex] * 0.5 * intensity;
positions[i3] = x * radius;
positions[i3 + 1] = y * radius;
positions[i3 + 2] = z * radius;
// Update colors based on mode
const colorShift = Math.sin(time * 0.5 + i * 0.001) * 0.1;
colors[i3] = 0.5 + x * 0.5 + colorShift;
colors[i3 + 1] = 0.3 + y * 0.7 + colorShift;
colors[i3 + 2] = 0.8 + z * 0.2 + colorShift;
}
this.particles.geometry.attributes.position.needsUpdate = true;
this.particles.geometry.attributes.color.needsUpdate = true;
// Rotate particles slowly
this.particles.rotation.y = time * 0.1;
this.particles.rotation.x = Math.sin(time * 0.05) * 0.1;
}
animate() {
this.animationFrameId = requestAnimationFrame(() => this.animate());
this.time += 0.016;
this.simulateAudioData();
this.updateParticles();
this.controls.update();
this.renderer.render(this.scene, this.camera);
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
destroy() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
if (this.controls) {
this.controls.dispose();
}
if (this.renderer) {
this.renderer.dispose();
}
}
}
// Initialize when page loads
window.addEventListener('load', () => {
updateStatus('Loading Neural AI Tutor...');
// Auto-expand sidebar on hover
const sidebar = document.getElementById('sidebar');
sidebar.addEventListener('mouseenter', () => {
sidebar.classList.add('expanded');
});
sidebar.addEventListener('mouseleave', () => {
sidebar.classList.remove('expanded');
});
// Wait a moment for libraries to load
setTimeout(() => {
// Initialize 3D visualization
window.visualization = new AITutorVisualization();
// Initialize chat interface
window.chatInterface = new ChatInterface();
// Initialize voice synthesis
if ('speechSynthesis' in window) {
window.voiceSynthesis = new VoiceSynthesis();
updateStatus('Voice synthesis initialized');
} else {
console.warn('Speech synthesis not supported in this browser');
document.getElementById('voiceStatus').style.display = 'flex';
document.getElementById('statusMessage').textContent = 'Voice synthesis not supported';
document.getElementById('statusMessage').style.color = '#ff5a5a';
}
if (window.visualization && window.visualization.scene) {
// Handle window resize
window.addEventListener('resize', () => window.visualization.onWindowResize());
// Handle key controls
window.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
window.visualization.setPreset(
window.visualization.params.mode === 'idle' ? 'listening' : 'idle'
);
}
// Ctrl+Space to stop speech
if (e.code === 'Space' && e.ctrlKey && window.voiceSynthesis) {
window.voiceSynthesis.stopAllSpeech();
}
// Escape key closes all panels
if (e.code === 'Escape') {
sidebar.classList.remove('expanded');
}
});
// Set initial preset
window.visualization.setPreset('idle');
// Auto-open visualization panel
setTimeout(() => {
togglePanel('visualizationPanel');
}, 1000);
}
}, 100);
});
// Handle page unload
window.addEventListener('beforeunload', () => {
if (window.visualization) {
window.visualization.destroy();
}
if (window.voiceSynthesis) {
window.voiceSynthesis.stopAllSpeech();
}
});
</script>
</body>
</html>