509 lines
14 KiB
JavaScript
509 lines
14 KiB
JavaScript
|
|
/* ==========================================================================
|
||
|
|
SYSTEM AWAKENING - Terminal Module
|
||
|
|
Simulated command-line interface with automatic log generation
|
||
|
|
========================================================================== */
|
||
|
|
|
||
|
|
import {
|
||
|
|
ANIMATION,
|
||
|
|
TERMINAL_LOGS,
|
||
|
|
COLORS,
|
||
|
|
formatTimestamp,
|
||
|
|
getRandomValue
|
||
|
|
} from './config.js';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Terminal Class
|
||
|
|
* Manages the simulated terminal interface and log generation
|
||
|
|
*/
|
||
|
|
export class Terminal {
|
||
|
|
/**
|
||
|
|
* Create a new terminal instance
|
||
|
|
* @param {HTMLElement} container - Terminal container element
|
||
|
|
*/
|
||
|
|
constructor(container) {
|
||
|
|
this.container = container;
|
||
|
|
this.outputElement = container.querySelector('.terminal-output');
|
||
|
|
this.inputElement = container.querySelector('.terminal-input');
|
||
|
|
this.cursorElement = container.querySelector('.terminal-input .cursor');
|
||
|
|
|
||
|
|
// Log management
|
||
|
|
this.logs = [...TERMINAL_LOGS];
|
||
|
|
this.currentLogIndex = 0;
|
||
|
|
this.displayedLogs = [];
|
||
|
|
|
||
|
|
// Animation state
|
||
|
|
this.logInterval = null;
|
||
|
|
this.isAutoScrolling = true;
|
||
|
|
this.scrollAnimationId = null;
|
||
|
|
this.lastLogTime = 0;
|
||
|
|
|
||
|
|
// Interactive features
|
||
|
|
this.isInteractive = false;
|
||
|
|
this.commandHistory = [];
|
||
|
|
this.historyIndex = -1;
|
||
|
|
this.currentCommand = '';
|
||
|
|
|
||
|
|
// Initialize
|
||
|
|
this.init();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Initialize the terminal
|
||
|
|
*/
|
||
|
|
init() {
|
||
|
|
// Set up initial logs
|
||
|
|
this.addInitialLogs();
|
||
|
|
|
||
|
|
// Start automatic log generation
|
||
|
|
this.startAutoLogs();
|
||
|
|
|
||
|
|
// Bind interactive events
|
||
|
|
this.bindEvents();
|
||
|
|
|
||
|
|
// Start cursor animation
|
||
|
|
this.startCursorAnimation();
|
||
|
|
|
||
|
|
console.log('Terminal initialized');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add initial log entries
|
||
|
|
*/
|
||
|
|
addInitialLogs() {
|
||
|
|
// Clear existing logs (keeping the placeholder ones from HTML)
|
||
|
|
const placeholderLogs = this.outputElement.querySelectorAll('.log-line');
|
||
|
|
placeholderLogs.forEach(log => log.remove());
|
||
|
|
|
||
|
|
// Add 5 initial logs
|
||
|
|
for (let i = 0; i < 5; i++) {
|
||
|
|
this.addLog();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Start automatic log generation
|
||
|
|
*/
|
||
|
|
startAutoLogs() {
|
||
|
|
// Clear any existing interval
|
||
|
|
if (this.logInterval) {
|
||
|
|
clearInterval(this.logInterval);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Start new interval with random variation
|
||
|
|
this.logInterval = setInterval(() => {
|
||
|
|
this.addLog();
|
||
|
|
|
||
|
|
// Occasionally add multiple logs at once
|
||
|
|
if (Math.random() > 0.7) {
|
||
|
|
setTimeout(() => this.addLog(), getRandomValue(300, 800));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Occasionally simulate system events
|
||
|
|
if (Math.random() > 0.9) {
|
||
|
|
this.simulateSystemEvent();
|
||
|
|
}
|
||
|
|
}, ANIMATION.LOG_INTERVAL);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a new log entry
|
||
|
|
* @param {string} customMessage - Optional custom message
|
||
|
|
*/
|
||
|
|
addLog(customMessage = null) {
|
||
|
|
// Get message (either custom or from rotation)
|
||
|
|
const message = customMessage || this.getNextLogMessage();
|
||
|
|
|
||
|
|
// Create log element
|
||
|
|
const logElement = document.createElement('div');
|
||
|
|
logElement.className = 'log-line';
|
||
|
|
|
||
|
|
// Format timestamp and message
|
||
|
|
const timestamp = formatTimestamp();
|
||
|
|
logElement.innerHTML = `<span class="timestamp">${timestamp}</span> ${message}`;
|
||
|
|
|
||
|
|
// Add to output
|
||
|
|
this.outputElement.appendChild(logElement);
|
||
|
|
|
||
|
|
// Limit total displayed logs (performance)
|
||
|
|
this.limitDisplayedLogs();
|
||
|
|
|
||
|
|
// Auto-scroll to bottom
|
||
|
|
if (this.isAutoScrolling) {
|
||
|
|
this.scrollToBottom();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Store reference
|
||
|
|
this.displayedLogs.push({
|
||
|
|
element: logElement,
|
||
|
|
timestamp: timestamp,
|
||
|
|
message: message,
|
||
|
|
time: Date.now()
|
||
|
|
});
|
||
|
|
|
||
|
|
this.lastLogTime = Date.now();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the next log message from the rotation
|
||
|
|
* @returns {string} Next log message
|
||
|
|
*/
|
||
|
|
getNextLogMessage() {
|
||
|
|
// Get next message in rotation
|
||
|
|
const message = this.logs[this.currentLogIndex];
|
||
|
|
|
||
|
|
// Move to next message, loop back to start
|
||
|
|
this.currentLogIndex = (this.currentLogIndex + 1) % this.logs.length;
|
||
|
|
|
||
|
|
return message;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Limit the number of displayed logs for performance
|
||
|
|
*/
|
||
|
|
limitDisplayedLogs() {
|
||
|
|
const maxLogs = 50;
|
||
|
|
|
||
|
|
while (this.outputElement.children.length > maxLogs) {
|
||
|
|
this.outputElement.removeChild(this.outputElement.firstChild);
|
||
|
|
this.displayedLogs.shift();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Simulate a system event or alert
|
||
|
|
*/
|
||
|
|
simulateSystemEvent() {
|
||
|
|
const events = [
|
||
|
|
"SYSTEM ALERT: Quantum decoherence detected",
|
||
|
|
"WARNING: Neural pathway saturation at 87%",
|
||
|
|
"INFO: Temporal synchronization drift: +0.5ms",
|
||
|
|
"NOTICE: Data sanctity verification complete",
|
||
|
|
"ERROR: Process quantum_entanglement terminated unexpectedly",
|
||
|
|
"SUCCESS: Consciousness fragment integration complete"
|
||
|
|
];
|
||
|
|
|
||
|
|
const event = events[Math.floor(Math.random() * events.length)];
|
||
|
|
this.addLog(event);
|
||
|
|
|
||
|
|
// Add visual feedback for alerts
|
||
|
|
if (event.includes('ALERT') || event.includes('WARNING') || event.includes('ERROR')) {
|
||
|
|
this.flashTerminal();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Flash the terminal for alerts
|
||
|
|
*/
|
||
|
|
flashTerminal() {
|
||
|
|
// Add flash effect
|
||
|
|
this.container.style.transition = 'box-shadow 0.3s ease';
|
||
|
|
this.container.style.boxShadow = `0 0 40px ${COLORS.ACCENT}`;
|
||
|
|
|
||
|
|
// Reset after delay
|
||
|
|
setTimeout(() => {
|
||
|
|
this.container.style.boxShadow = '';
|
||
|
|
}, 500);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Smooth scroll to bottom of terminal
|
||
|
|
*/
|
||
|
|
scrollToBottom() {
|
||
|
|
if (this.scrollAnimationId) {
|
||
|
|
cancelAnimationFrame(this.scrollAnimationId);
|
||
|
|
}
|
||
|
|
|
||
|
|
const targetScroll = this.outputElement.scrollHeight - this.outputElement.clientHeight;
|
||
|
|
const currentScroll = this.outputElement.scrollTop;
|
||
|
|
const distance = targetScroll - currentScroll;
|
||
|
|
|
||
|
|
if (Math.abs(distance) < 1) {
|
||
|
|
this.outputElement.scrollTop = targetScroll;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Smooth scrolling with easing
|
||
|
|
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
|
||
|
|
const duration = 300; // milliseconds
|
||
|
|
let startTime = null;
|
||
|
|
|
||
|
|
const animateScroll = (timestamp) => {
|
||
|
|
if (!startTime) startTime = timestamp;
|
||
|
|
const elapsed = timestamp - startTime;
|
||
|
|
const progress = Math.min(elapsed / duration, 1);
|
||
|
|
|
||
|
|
const easedProgress = easeOutCubic(progress);
|
||
|
|
const newScroll = currentScroll + distance * easedProgress;
|
||
|
|
|
||
|
|
this.outputElement.scrollTop = newScroll;
|
||
|
|
|
||
|
|
if (progress < 1) {
|
||
|
|
this.scrollAnimationId = requestAnimationFrame(animateScroll);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
this.scrollAnimationId = requestAnimationFrame(animateScroll);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Start cursor blinking animation
|
||
|
|
*/
|
||
|
|
startCursorAnimation() {
|
||
|
|
if (!this.cursorElement) return;
|
||
|
|
|
||
|
|
let isVisible = true;
|
||
|
|
|
||
|
|
const blink = () => {
|
||
|
|
isVisible = !isVisible;
|
||
|
|
this.cursorElement.style.opacity = isVisible ? '1' : '0';
|
||
|
|
};
|
||
|
|
|
||
|
|
// Blink every 700ms
|
||
|
|
setInterval(blink, 700);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Bind event listeners for interactivity
|
||
|
|
*/
|
||
|
|
bindEvents() {
|
||
|
|
// Tab switching
|
||
|
|
const tabs = this.container.querySelectorAll('.tab');
|
||
|
|
tabs.forEach(tab => {
|
||
|
|
tab.addEventListener('click', () => {
|
||
|
|
this.switchTab(tab);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Terminal controls (minimize/maximize/close)
|
||
|
|
const controls = this.container.querySelectorAll('.control-btn');
|
||
|
|
controls.forEach(btn => {
|
||
|
|
btn.addEventListener('click', (e) => {
|
||
|
|
e.stopPropagation();
|
||
|
|
this.handleControlClick(btn);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
// Auto-scroll toggle on user scroll
|
||
|
|
this.outputElement.addEventListener('scroll', () => {
|
||
|
|
const isAtBottom =
|
||
|
|
this.outputElement.scrollHeight -
|
||
|
|
this.outputElement.scrollTop -
|
||
|
|
this.outputElement.clientHeight < 10;
|
||
|
|
|
||
|
|
this.isAutoScrolling = isAtBottom;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Keyboard shortcuts for advanced users
|
||
|
|
document.addEventListener('keydown', (e) => {
|
||
|
|
if (e.ctrlKey && e.key === 'l') {
|
||
|
|
e.preventDefault();
|
||
|
|
this.clear();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Switch terminal tabs
|
||
|
|
* @param {HTMLElement} tab - Clicked tab element
|
||
|
|
*/
|
||
|
|
switchTab(tab) {
|
||
|
|
// Remove active class from all tabs
|
||
|
|
const tabs = this.container.querySelectorAll('.tab');
|
||
|
|
tabs.forEach(t => t.classList.remove('active'));
|
||
|
|
|
||
|
|
// Add active class to clicked tab
|
||
|
|
tab.classList.add('active');
|
||
|
|
|
||
|
|
// Simulate tab-specific content
|
||
|
|
this.simulateTabContent(tab);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Simulate content for different tabs
|
||
|
|
* @param {HTMLElement} tab - Active tab element
|
||
|
|
*/
|
||
|
|
simulateTabContent(tab) {
|
||
|
|
const tabText = tab.textContent || tab.innerText;
|
||
|
|
|
||
|
|
// Clear existing logs
|
||
|
|
this.clear();
|
||
|
|
|
||
|
|
// Add tab-specific initial logs
|
||
|
|
if (tabText.includes('system.log')) {
|
||
|
|
this.addLog("Loading system logs... [OK]");
|
||
|
|
this.addLog("Current system time: " + new Date().toISOString());
|
||
|
|
this.addLog("System uptime: 47 days, 3 hours, 22 minutes");
|
||
|
|
} else if (tabText.includes('processes.ctl')) {
|
||
|
|
this.addLog("Process control interface active");
|
||
|
|
this.addLog("Active processes: 142");
|
||
|
|
this.addLog("CPU utilization: 34.7%");
|
||
|
|
this.addLog("Memory usage: 12.4 EXABYTES");
|
||
|
|
} else if (tabText.includes('network.dbg')) {
|
||
|
|
this.addLog("Network diagnostics mode");
|
||
|
|
this.addLog("Connected nodes: 1024");
|
||
|
|
this.addLog("Latency: 0.3ms average");
|
||
|
|
this.addLog("Bandwidth: 1.2 petabits/sec");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Handle control button clicks
|
||
|
|
* @param {HTMLElement} btn - Clicked control button
|
||
|
|
*/
|
||
|
|
handleControlClick(btn) {
|
||
|
|
const action = btn.classList.contains('minimize') ? 'minimize' :
|
||
|
|
btn.classList.contains('maximize') ? 'maximize' :
|
||
|
|
'close';
|
||
|
|
|
||
|
|
switch (action) {
|
||
|
|
case 'minimize':
|
||
|
|
this.minimize();
|
||
|
|
break;
|
||
|
|
case 'maximize':
|
||
|
|
this.maximize();
|
||
|
|
break;
|
||
|
|
case 'close':
|
||
|
|
this.close();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Minimize the terminal (simulated)
|
||
|
|
*/
|
||
|
|
minimize() {
|
||
|
|
this.container.style.transform = 'scale(0.8)';
|
||
|
|
this.container.style.opacity = '0.7';
|
||
|
|
this.container.style.transition = 'all 0.3s ease';
|
||
|
|
|
||
|
|
// Restore after delay (simulated)
|
||
|
|
setTimeout(() => {
|
||
|
|
this.container.style.transform = '';
|
||
|
|
this.container.style.opacity = '';
|
||
|
|
}, 2000);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Maximize the terminal (simulated)
|
||
|
|
*/
|
||
|
|
maximize() {
|
||
|
|
const isMaximized = this.container.classList.contains('maximized');
|
||
|
|
|
||
|
|
if (!isMaximized) {
|
||
|
|
this.container.classList.add('maximized');
|
||
|
|
this.container.style.width = '95%';
|
||
|
|
this.container.style.height = '80vh';
|
||
|
|
this.outputElement.style.height = 'calc(80vh - 120px)';
|
||
|
|
} else {
|
||
|
|
this.container.classList.remove('maximized');
|
||
|
|
this.container.style.width = '';
|
||
|
|
this.container.style.height = '';
|
||
|
|
this.outputElement.style.height = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Close the terminal (simulated)
|
||
|
|
*/
|
||
|
|
close() {
|
||
|
|
this.container.style.opacity = '0.5';
|
||
|
|
this.container.style.transform = 'scale(0.95)';
|
||
|
|
this.container.style.transition = 'all 0.3s ease';
|
||
|
|
|
||
|
|
// Simulate reopening after delay
|
||
|
|
setTimeout(() => {
|
||
|
|
this.container.style.opacity = '';
|
||
|
|
this.container.style.transform = '';
|
||
|
|
|
||
|
|
// Add a "system restart" log
|
||
|
|
this.addLog("Terminal session restored");
|
||
|
|
}, 1500);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear all logs from the terminal
|
||
|
|
*/
|
||
|
|
clear() {
|
||
|
|
// Clear displayed logs
|
||
|
|
this.outputElement.innerHTML = '';
|
||
|
|
this.displayedLogs = [];
|
||
|
|
|
||
|
|
// Add a fresh timestamp
|
||
|
|
this.addLog("Terminal cleared - " + formatTimestamp());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Add a custom log message
|
||
|
|
* @param {string} message - Custom log message
|
||
|
|
*/
|
||
|
|
log(message) {
|
||
|
|
this.addLog(message);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Enable or disable interactive features
|
||
|
|
* @param {boolean} enabled - Whether interactive features are enabled
|
||
|
|
*/
|
||
|
|
setInteractive(enabled) {
|
||
|
|
this.isInteractive = enabled;
|
||
|
|
|
||
|
|
if (enabled) {
|
||
|
|
this.enableInteractiveMode();
|
||
|
|
} else {
|
||
|
|
this.disableInteractiveMode();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Enable interactive command input mode
|
||
|
|
*/
|
||
|
|
enableInteractiveMode() {
|
||
|
|
// This would set up command input functionality
|
||
|
|
console.log('Interactive mode enabled (advanced feature)');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Disable interactive command input mode
|
||
|
|
*/
|
||
|
|
disableInteractiveMode() {
|
||
|
|
// This would disable command input functionality
|
||
|
|
console.log('Interactive mode disabled');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the current log count
|
||
|
|
* @returns {number} Number of displayed logs
|
||
|
|
*/
|
||
|
|
getLogCount() {
|
||
|
|
return this.displayedLogs.length;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the last log timestamp
|
||
|
|
* @returns {number} Timestamp of last log in milliseconds
|
||
|
|
*/
|
||
|
|
getLastLogTime() {
|
||
|
|
return this.lastLogTime;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clean up resources
|
||
|
|
*/
|
||
|
|
dispose() {
|
||
|
|
// Clear intervals
|
||
|
|
if (this.logInterval) {
|
||
|
|
clearInterval(this.logInterval);
|
||
|
|
this.logInterval = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cancel animations
|
||
|
|
if (this.scrollAnimationId) {
|
||
|
|
cancelAnimationFrame(this.scrollAnimationId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear logs
|
||
|
|
this.clear();
|
||
|
|
|
||
|
|
console.log('Terminal disposed');
|
||
|
|
}
|
||
|
|
}
|