628 lines
24 KiB
JavaScript
628 lines
24 KiB
JavaScript
/**
|
|
* Academy UI Manager
|
|
* Handles level selection, checkpoint modals, and integration with campaign/preference/library systems
|
|
*/
|
|
|
|
class AcademyUI {
|
|
constructor() {
|
|
this.currentModal = null;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
console.log('🎓 Academy UI Manager initialized');
|
|
}
|
|
|
|
/**
|
|
* Show level selection screen
|
|
* Displays all 30 levels with locked/unlocked states
|
|
*/
|
|
showLevelSelect() {
|
|
const unlockedLevels = window.campaignManager.getUnlockedLevels();
|
|
const completedLevels = window.gameData.academyProgress.completedLevels;
|
|
const currentLevel = window.gameData.academyProgress.currentLevel;
|
|
|
|
let html = `
|
|
<div class="level-select-screen">
|
|
<div class="level-select-header">
|
|
<h2>🎓 The Academy - Campaign</h2>
|
|
<p class="progress-subtitle">Select your next level</p>
|
|
<div class="campaign-progress">
|
|
<div class="progress-bar-container">
|
|
<div class="progress-bar-fill" style="width: ${(completedLevels.length / 30 * 100).toFixed(1)}%"></div>
|
|
</div>
|
|
<p class="progress-text">${completedLevels.length}/30 Levels Complete</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="level-grid">
|
|
`;
|
|
|
|
// Generate 30 level buttons
|
|
for (let i = 1; i <= 30; i++) {
|
|
const isUnlocked = unlockedLevels.includes(i);
|
|
const isCompleted = completedLevels.includes(i);
|
|
const isCurrent = i === currentLevel;
|
|
const isCheckpoint = window.campaignManager.isCheckpointLevel(i);
|
|
const arc = window.campaignManager.getArcForLevel(i);
|
|
|
|
const statusClass = isCompleted ? 'completed' :
|
|
isUnlocked ? 'unlocked' : 'locked';
|
|
|
|
const icon = isCompleted ? '✓' :
|
|
isCheckpoint ? '⚡' :
|
|
isUnlocked ? '▶' : '🔒';
|
|
|
|
html += `
|
|
<button
|
|
class="level-button ${statusClass} ${isCurrent ? 'current' : ''}"
|
|
onclick="window.academyUI.selectLevel(${i})"
|
|
${!isUnlocked ? 'disabled' : ''}
|
|
title="${arc} Arc${isCheckpoint ? ' - Checkpoint' : ''}${isCompleted ? ' - Completed' : ''}">
|
|
<div class="level-number">${i}</div>
|
|
<div class="level-icon">${icon}</div>
|
|
${isCheckpoint ? '<div class="checkpoint-badge">⚡</div>' : ''}
|
|
</button>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
</div>
|
|
|
|
<div class="level-info-panel" id="level-info-panel">
|
|
<p class="info-hint">Select a level to view details</p>
|
|
</div>
|
|
|
|
<div class="level-select-footer">
|
|
<button onclick="window.location.href='index.html'" class="btn-back">🏠 Back to Menu</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Show in the academy setup container
|
|
const setupContainer = document.getElementById('academy-setup');
|
|
if (setupContainer) {
|
|
setupContainer.innerHTML = html;
|
|
setupContainer.style.display = 'block';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select a level and show its details
|
|
* @param {number} levelNum - Level to select
|
|
*/
|
|
selectLevel(levelNum) {
|
|
const arc = window.campaignManager.getArcForLevel(levelNum);
|
|
const isCheckpoint = window.campaignManager.isCheckpointLevel(levelNum);
|
|
const isCompleted = window.campaignManager.isLevelCompleted(levelNum);
|
|
|
|
// Show level details in info panel
|
|
const infoPanelContent = this.getLevelInfoHTML(levelNum, arc, isCheckpoint, isCompleted);
|
|
const infoPanel = document.getElementById('level-info-panel');
|
|
if (infoPanel) {
|
|
infoPanel.innerHTML = infoPanelContent;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get level info HTML
|
|
* @param {number} levelNum - Level number
|
|
* @param {string} arc - Arc name
|
|
* @param {boolean} isCheckpoint - Is checkpoint level
|
|
* @param {boolean} isCompleted - Is completed
|
|
* @returns {string} HTML content
|
|
*/
|
|
getLevelInfoHTML(levelNum, arc, isCheckpoint, isCompleted) {
|
|
return `
|
|
<div class="level-details">
|
|
<h3>Level ${levelNum}: ${this.getLevelTitle(levelNum)}</h3>
|
|
<div class="level-meta">
|
|
<span class="arc-badge">${arc} Arc</span>
|
|
${isCheckpoint ? '<span class="checkpoint-badge-large">⚡ Checkpoint</span>' : ''}
|
|
${isCompleted ? '<span class="completed-badge">✓ Completed</span>' : ''}
|
|
</div>
|
|
<p class="level-description">${this.getLevelDescription(levelNum)}</p>
|
|
${isCheckpoint ? '<p class="checkpoint-notice">💡 You will be asked to update your preferences before starting this level.</p>' : ''}
|
|
<button onclick="window.academyUI.startLevel(${levelNum})" class="btn-start-level">
|
|
${isCompleted ? '🔄 Replay Level' : '▶️ Start Level'}
|
|
</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Get level title based on number
|
|
* @param {number} levelNum - Level number
|
|
* @returns {string} Level title
|
|
*/
|
|
getLevelTitle(levelNum) {
|
|
const titles = {
|
|
1: "First Edge",
|
|
5: "Foundation Complete",
|
|
10: "Feature Discovery Complete",
|
|
15: "Deepening",
|
|
20: "Mastery",
|
|
25: "Final Challenge",
|
|
30: "Graduation"
|
|
};
|
|
return titles[levelNum] || `Training Session ${levelNum}`;
|
|
}
|
|
|
|
/**
|
|
* Get level description
|
|
* @param {number} levelNum - Level number
|
|
* @returns {string} Level description
|
|
*/
|
|
getLevelDescription(levelNum) {
|
|
if (levelNum === 1) return "Welcome to The Academy. Begin your journey with basic edging training.";
|
|
if (levelNum === 5) return "Complete the Foundation arc. Test your progress with a checkpoint session.";
|
|
if (levelNum === 10) return "Feature Discovery complete. Time to optimize your preferences.";
|
|
if (levelNum === 15) return "Halfway through. Deepen your training with advanced techniques.";
|
|
if (levelNum === 20) return "Enter the Mastery arc. Push your limits further.";
|
|
if (levelNum === 25) return "Final checkpoint before graduation. Prepare for the ultimate test.";
|
|
if (levelNum === 30) return "The final test. Graduate from The Academy.";
|
|
return "Continue your training journey with this session.";
|
|
}
|
|
|
|
/**
|
|
* Start a level (with checkpoint modal if needed)
|
|
* @param {number} levelNum - Level to start
|
|
*/
|
|
startLevel(levelNum) {
|
|
console.log(`🎯 Starting Level ${levelNum}...`);
|
|
|
|
// Check if this is a checkpoint level
|
|
if (window.campaignManager.isCheckpointLevel(levelNum)) {
|
|
this.showCheckpointModal(levelNum);
|
|
} else {
|
|
this.beginLevel(levelNum);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show checkpoint preference modal
|
|
* @param {number} levelNum - Checkpoint level number
|
|
*/
|
|
showCheckpointModal(levelNum) {
|
|
const config = window.preferenceManager.getCheckpointModalConfig(levelNum);
|
|
|
|
if (!config) {
|
|
console.error('No checkpoint config for level', levelNum);
|
|
this.beginLevel(levelNum);
|
|
return;
|
|
}
|
|
|
|
const modalHTML = this.buildCheckpointModalHTML(levelNum, config);
|
|
|
|
// Create modal overlay
|
|
const overlay = document.createElement('div');
|
|
overlay.className = 'checkpoint-modal-overlay';
|
|
overlay.innerHTML = modalHTML;
|
|
document.body.appendChild(overlay);
|
|
|
|
this.currentModal = overlay;
|
|
|
|
// Initialize tabs
|
|
this.initCheckpointModalTabs(levelNum, config);
|
|
}
|
|
|
|
/**
|
|
* Build checkpoint modal HTML
|
|
* @param {number} levelNum - Level number
|
|
* @param {Object} config - Modal configuration
|
|
* @returns {string} Modal HTML
|
|
*/
|
|
buildCheckpointModalHTML(levelNum, config) {
|
|
return `
|
|
<div class="checkpoint-modal">
|
|
<div class="checkpoint-header">
|
|
<h2>${config.title}</h2>
|
|
<p>${config.description}</p>
|
|
</div>
|
|
|
|
<div class="checkpoint-tabs">
|
|
<button class="tab-btn active" data-tab="preferences">🎯 Preferences</button>
|
|
<button class="tab-btn" data-tab="library">📚 Library</button>
|
|
</div>
|
|
|
|
<div class="checkpoint-content">
|
|
<div class="tab-panel active" id="preferences-panel">
|
|
<div id="preferences-editor"></div>
|
|
</div>
|
|
<div class="tab-panel" id="library-panel">
|
|
<div id="library-manager"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="checkpoint-footer">
|
|
<button onclick="window.academyUI.closeCheckpointModal()" class="btn-secondary">
|
|
Skip for Now
|
|
</button>
|
|
<button onclick="window.academyUI.saveCheckpointAndStart(${levelNum})" class="btn-primary">
|
|
Save & Start Level
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Initialize checkpoint modal tabs
|
|
* @param {number} levelNum - Level number
|
|
* @param {Object} config - Modal configuration
|
|
*/
|
|
initCheckpointModalTabs(levelNum, config) {
|
|
// Tab switching
|
|
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
tabBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const tabName = btn.dataset.tab;
|
|
|
|
// Update active states
|
|
tabBtns.forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
|
|
document.querySelectorAll('.tab-panel').forEach(panel => {
|
|
panel.classList.remove('active');
|
|
});
|
|
document.getElementById(`${tabName}-panel`).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// Populate preferences editor
|
|
this.populatePreferencesEditor(config.categories);
|
|
|
|
// Populate library manager
|
|
this.populateLibraryManager();
|
|
}
|
|
|
|
/**
|
|
* Populate preferences editor
|
|
* @param {Array<string>} categories - Categories to show
|
|
*/
|
|
populatePreferencesEditor(categories) {
|
|
const container = document.getElementById('preferences-editor');
|
|
if (!container) return;
|
|
|
|
let html = '';
|
|
|
|
categories.forEach(category => {
|
|
const prefs = window.preferenceManager.getCategoryPreferences(category);
|
|
html += `<div class="pref-category">`;
|
|
html += `<h3>${this.getCategoryDisplayName(category)}</h3>`;
|
|
html += this.buildCategoryEditor(category, prefs);
|
|
html += `</div>`;
|
|
});
|
|
|
|
container.innerHTML = html;
|
|
}
|
|
|
|
/**
|
|
* Get category display name
|
|
* @param {string} category - Category key
|
|
* @returns {string} Display name
|
|
*/
|
|
getCategoryDisplayName(category) {
|
|
const names = {
|
|
contentThemes: '🎭 Content Themes',
|
|
visualPreferences: '👁️ Visual Preferences',
|
|
intensity: '⚡ Intensity Levels',
|
|
captionTone: '💬 Caption Tone',
|
|
audioPreferences: '🔊 Audio Preferences',
|
|
sessionDuration: '⏱️ Session Duration',
|
|
featurePreferences: '🎮 Feature Preferences',
|
|
boundaries: '🛡️ Boundaries'
|
|
};
|
|
return names[category] || category;
|
|
}
|
|
|
|
/**
|
|
* Build category editor HTML
|
|
* @param {string} category - Category name
|
|
* @param {Object} prefs - Preferences object
|
|
* @returns {string} Editor HTML
|
|
*/
|
|
buildCategoryEditor(category, prefs) {
|
|
if (category === 'intensity') {
|
|
return this.buildIntensitySliders(prefs);
|
|
} else if (category === 'sessionDuration') {
|
|
return this.buildDurationSelector(prefs);
|
|
} else {
|
|
return this.buildCheckboxes(category, prefs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build intensity sliders
|
|
* @param {Object} prefs - Intensity preferences
|
|
* @returns {string} HTML
|
|
*/
|
|
buildIntensitySliders(prefs) {
|
|
let html = '';
|
|
for (const [key, value] of Object.entries(prefs)) {
|
|
html += `
|
|
<div class="slider-group">
|
|
<label>${key.replace(/([A-Z])/g, ' $1').trim()}: <span id="${key}-value">${value}</span></label>
|
|
<input type="range" min="1" max="5" value="${value}"
|
|
data-category="intensity" data-key="${key}"
|
|
oninput="document.getElementById('${key}-value').textContent = this.value">
|
|
</div>
|
|
`;
|
|
}
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Build duration selector
|
|
* @param {Object} prefs - Duration preferences
|
|
* @returns {string} HTML
|
|
*/
|
|
buildDurationSelector(prefs) {
|
|
const options = ['short', 'medium', 'long', 'marathon'];
|
|
let html = '<select data-category="sessionDuration" data-key="preferred">';
|
|
options.forEach(opt => {
|
|
html += `<option value="${opt}" ${prefs.preferred === opt ? 'selected' : ''}>${opt}</option>`;
|
|
});
|
|
html += '</select>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Build checkboxes for category
|
|
* @param {string} category - Category name
|
|
* @param {Object} prefs - Preferences object
|
|
* @returns {string} HTML
|
|
*/
|
|
buildCheckboxes(category, prefs) {
|
|
let html = '<div class="checkbox-grid">';
|
|
for (const [key, value] of Object.entries(prefs)) {
|
|
if (value !== null) { // Skip undiscovered features
|
|
html += `
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" ${value ? 'checked' : ''}
|
|
data-category="${category}" data-key="${key}">
|
|
${key}
|
|
</label>
|
|
`;
|
|
}
|
|
}
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Populate library manager
|
|
*/
|
|
populateLibraryManager() {
|
|
const container = document.getElementById('library-manager');
|
|
if (!container) return;
|
|
|
|
// Sync with existing library first
|
|
const syncResult = window.libraryManager.syncWithExistingLibrary();
|
|
const stats = window.libraryManager.getLibraryStats();
|
|
|
|
container.innerHTML = `
|
|
<div class="library-stats">
|
|
<h3>📊 Library Statistics</h3>
|
|
<div class="stats-grid">
|
|
<div class="stat-item">
|
|
<span class="stat-label">Total Videos</span>
|
|
<span class="stat-value">${stats.totalItems}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Tagged</span>
|
|
<span class="stat-value">${stats.taggedItems}</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Avg Rating</span>
|
|
<span class="stat-value">${stats.averageRating}⭐</span>
|
|
</div>
|
|
<div class="stat-item">
|
|
<span class="stat-label">Favorites</span>
|
|
<span class="stat-value">${stats.totalFavorites}</span>
|
|
</div>
|
|
</div>
|
|
<p class="library-hint">
|
|
💡 Your existing video library (${syncResult.total} videos) is automatically imported.
|
|
You can add tags and ratings to organize your content.
|
|
</p>
|
|
<p class="library-hint">
|
|
📚 Manage your full library from the main menu Library tab for more detailed controls.
|
|
</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Save checkpoint preferences and start level
|
|
* @param {number} levelNum - Level to start
|
|
*/
|
|
saveCheckpointAndStart(levelNum) {
|
|
console.log('💾 Saving checkpoint preferences...');
|
|
|
|
// Collect all preference changes
|
|
const updates = {};
|
|
|
|
// Get all checkboxes
|
|
document.querySelectorAll('input[type="checkbox"][data-category]').forEach(checkbox => {
|
|
const category = checkbox.dataset.category;
|
|
const key = checkbox.dataset.key;
|
|
|
|
if (!updates[category]) updates[category] = {};
|
|
updates[category][key] = checkbox.checked;
|
|
});
|
|
|
|
// Get all sliders
|
|
document.querySelectorAll('input[type="range"][data-category]').forEach(slider => {
|
|
const category = slider.dataset.category;
|
|
const key = slider.dataset.key;
|
|
|
|
if (!updates[category]) updates[category] = {};
|
|
updates[category][key] = parseInt(slider.value);
|
|
});
|
|
|
|
// Get all selects
|
|
document.querySelectorAll('select[data-category]').forEach(select => {
|
|
const category = select.dataset.category;
|
|
const key = select.dataset.key;
|
|
|
|
if (!updates[category]) updates[category] = {};
|
|
updates[category][key] = select.value;
|
|
});
|
|
|
|
// Save to preferenceManager
|
|
let saved = true;
|
|
if (Object.keys(updates).length > 0) {
|
|
saved = window.preferenceManager.updateMultipleCategories(updates, levelNum);
|
|
if (saved) {
|
|
console.log('✅ Preferences updated:', updates);
|
|
} else {
|
|
console.error('❌ Failed to save preferences');
|
|
alert('⚠️ Unable to save preferences. Storage may be full.\n\nPlease restart the app and try again.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Close modal and begin level
|
|
this.closeCheckpointModal();
|
|
this.beginLevel(levelNum);
|
|
}
|
|
|
|
/**
|
|
* Close checkpoint modal
|
|
*/
|
|
closeCheckpointModal() {
|
|
if (this.currentModal) {
|
|
this.currentModal.remove();
|
|
this.currentModal = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Begin level (after checkpoint or directly)
|
|
* @param {number} levelNum - Level to start
|
|
*/
|
|
beginLevel(levelNum) {
|
|
console.log(`🚀 Beginning Level ${levelNum}...`);
|
|
|
|
// Use campaignManager to start level
|
|
const levelConfig = window.campaignManager.startLevel(levelNum);
|
|
|
|
if (!levelConfig) {
|
|
alert('Unable to start this level. Please check requirements.');
|
|
return;
|
|
}
|
|
|
|
// Hide level select screen
|
|
const setupContainer = document.getElementById('academy-setup');
|
|
if (setupContainer) {
|
|
setupContainer.style.display = 'none';
|
|
}
|
|
|
|
// Start the actual training session
|
|
// This would integrate with existing training-academy.html session logic
|
|
this.startTrainingSession(levelConfig);
|
|
}
|
|
|
|
/**
|
|
* Start training session with level config
|
|
* @param {Object} levelConfig - Level configuration
|
|
*/
|
|
startTrainingSession(levelConfig) {
|
|
console.log('🎓 Starting training session with config:', levelConfig);
|
|
|
|
// Get detailed level configuration from academyLevelData
|
|
const detailedConfig = window.academyLevelData.getLevelConfig(levelConfig.levelNumber);
|
|
if (!detailedConfig) {
|
|
console.error('⚠️ No level data found for level:', levelConfig.levelNumber);
|
|
return;
|
|
}
|
|
|
|
console.log('📊 Detailed level config:', detailedConfig);
|
|
|
|
// Get content filter based on user preferences
|
|
const preferenceFilter = window.preferenceManager.getContentFilter();
|
|
|
|
// Sync with existing library to ensure we have latest videos
|
|
window.libraryManager.syncWithExistingLibrary();
|
|
|
|
// Get media requirements for this level
|
|
const mediaRequirements = window.academyLevelData.getMediaForLevel(
|
|
levelConfig.levelNumber,
|
|
window.preferenceManager.getPreferences()
|
|
);
|
|
|
|
console.log('🎬 Media requirements:', mediaRequirements);
|
|
|
|
// Get filtered media from library
|
|
let mediaItems = window.libraryManager.getMediaForPreferences(
|
|
mediaRequirements.videoFilter,
|
|
mediaRequirements.videoCount
|
|
);
|
|
|
|
// Fallback to all videos if no matches
|
|
if (mediaItems.length === 0) {
|
|
console.warn('⚠️ No media items matched preferences. Using all available videos.');
|
|
mediaItems = window.libraryManager.getAllVideosWithMetadata();
|
|
}
|
|
|
|
console.log('🎬 Loaded media for session:', mediaItems.length, 'items');
|
|
console.log('🎯 Preference filter:', preferenceFilter);
|
|
console.log('⏱️ Session duration:', detailedConfig.session.duration);
|
|
console.log('📋 Tasks:', detailedConfig.tasks.length);
|
|
console.log('🎯 Objectives:', detailedConfig.objectives);
|
|
|
|
// Sync videos to videoPlayerManager for video playback
|
|
if (window.videoPlayerManager && mediaItems.length > 0) {
|
|
const videoPaths = mediaItems.map(item => item.path || item.fullPath);
|
|
window.videoPlayerManager.videoLibrary.background = videoPaths;
|
|
window.videoPlayerManager.videoLibrary.task = videoPaths;
|
|
window.videoPlayerManager.videoLibrary.reward = videoPaths;
|
|
window.videoPlayerManager.videoLibrary.punishment = videoPaths;
|
|
console.log(`🔄 Synced ${videoPaths.length} videos to videoPlayerManager for Level ${levelConfig.levelNumber}`);
|
|
}
|
|
|
|
// Update trainingVideoLibrary global for training-academy.html
|
|
if (mediaItems.length > 0) {
|
|
window.trainingVideoLibrary = mediaItems;
|
|
console.log(`📹 Set trainingVideoLibrary with ${mediaItems.length} videos`);
|
|
}
|
|
|
|
// Enable video if this level requires it (has video media requirements)
|
|
if (detailedConfig.media && detailedConfig.media.videos && detailedConfig.media.videos.required > 0) {
|
|
// Load and update settings to enable video
|
|
const settings = typeof window.loadAcademySettings === 'function'
|
|
? window.loadAcademySettings()
|
|
: { enableVideo: false };
|
|
|
|
if (!settings.enableVideo) {
|
|
console.log('🎬 Level requires video - enabling video in settings');
|
|
settings.enableVideo = true;
|
|
if (typeof window.saveAcademySettings === 'function') {
|
|
window.saveAcademySettings(settings);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the selected training mode to trigger proper training flow
|
|
window.selectedTrainingMode = 'training-academy';
|
|
|
|
// Store current level info globally for access during session
|
|
window.currentAcademyLevel = {
|
|
levelNumber: levelConfig.levelNumber,
|
|
config: detailedConfig,
|
|
mediaRequirements: mediaRequirements
|
|
};
|
|
|
|
// Call the existing training session starter
|
|
if (typeof window.startTrainingSession === 'function') {
|
|
window.startTrainingSession();
|
|
} else {
|
|
console.error('⚠️ startTrainingSession() not found in window scope');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create global instance
|
|
window.academyUI = new AcademyUI();
|