class TutorialModule { constructor() { this.commands = {}; this.terminal = null; this.STORAGE_KEY = 'webx11_tutorial_state'; this.COMPLETE_KEY = 'webx11_tutorial_complete'; this.SKIP_KEY = 'webx11_tutorial_skipped'; this._maxTries = 3; this._initState(); } _initState() { // Restore saved state or start fresh const saved = localStorage.getItem(this.STORAGE_KEY); if (saved) { try { const s = JSON.parse(saved); this._completed = new Set(s.completed || []); this._activeIndex = s.activeIndex || 0; this._tries = s.tries || {}; this._started = s.started || false; return; } catch(e) { /* corrupt, reset */ } } this._completed = new Set(); this._activeIndex = 0; this._tries = {}; this._started = false; } _saveState() { try { localStorage.setItem(this.STORAGE_KEY, JSON.stringify({ completed: Array.from(this._completed), activeIndex: this._activeIndex, tries: this._tries, started: this._started })); } catch(e) { /* quota */ } } _getQuests() { return [ { id: 1, title: 'Your First Command', desc: 'Type `help` to see every command available in newOS4.', check: (cmd) => cmd === 'help', hint: 'Just type: help' }, { id: 2, title: 'Explore the Filesystem', desc: 'Type `ls` to list files in the current directory.', check: (cmd) => cmd === 'ls', hint: 'Type: ls' }, { id: 3, title: 'Create Something', desc: 'Use `touch hello.txt` to create your first file.', check: (cmd) => cmd === 'touch', hint: 'Type: touch hello.txt' }, { id: 4, title: 'Write to a File', desc: 'Open the editor with `edit hello.txt`, type something, and click Save.', check: (cmd, term) => { const c = term && term.fs && term.fs.readFile('hello.txt'); return c && c.length > 0; }, hint: 'Type: edit hello.txt, then write something and click 💾 Save & Test' }, { id: 5, title: 'Talk to the AI', desc: 'Type `ai "hello"` to greet the AI assistant.', check: (cmd) => cmd === 'ai' || cmd === 'ask', hint: 'Type: ai "hello"' }, { id: 6, title: 'Check Your Progress', desc: 'Type `status` to see how the system is doing.', check: (cmd) => cmd === 'status', hint: 'Type: status' }, ]; } requirements() { return { met: true, missing: [] }; } getHelpInfo() { return { 'tutorial': 'tutorial [start|skip|status] — Interactive guided tour of newOS4 (6 quests)', }; } registerCommands() { this.commands['tutorial'] = (args) => this._handleTutorial(args); } // ── Public API called by terminal.js ── /** Called after every command execution to check quest progress */ onCommandExecuted(cmd, args) { if (!this._started || this.isComplete()) return; const quests = this._getQuests(); const current = quests[this._activeIndex]; if (!current || this._completed.has(current.id)) return; // Special handling: quest 4 (edit) — don't count as wrong, wait for save if (current.id === 4 && cmd === 'edit') { this.terminal.print(' ✅ Editor opened! Write something and click Save.', 'success'); return; // Don't fail, don't complete — wait for onFileSaved } // Check if this command completes the current quest if (current.check(cmd, this.terminal)) { this._completeCurrentQuest(); } else { // Wrong command — increment try count const count = (this._tries[current.id] || 0) + 1; this._tries[current.id] = count; this._saveState(); if (count >= this._maxTries) { this.terminal.print(`💡 Hint: ${current.hint}`, 'warning'); this._tries[current.id] = 0; // reset so hint repeats } } } /** Called by editor module after a file is saved via the modal */ onFileSaved(path) { if (!this._started || this.isComplete()) return; const quests = this._getQuests(); const current = quests[this._activeIndex]; if (!current || current.id !== 4 || this._completed.has(current.id)) return; if (current.check('__file_saved__', this.terminal)) { this._completeCurrentQuest(); } } _completeCurrentQuest() { const quests = this._getQuests(); const current = quests[this._activeIndex]; this._completed.add(current.id); const nextIdx = this._activeIndex + 1; const next = quests[nextIdx]; if (next) { this._activeIndex = nextIdx; this._saveState(); this._showQuestNext(next); } else { this._activeIndex = nextIdx; this._saveState(); this._showCompletion(); } } isComplete() { return localStorage.getItem(this.COMPLETE_KEY) !== null; } isSkipped() { return localStorage.getItem(this.SKIP_KEY) !== null; } isStarted() { return this._started; } /** Called by welcome modal to begin the tutorial */ startTutorial() { // Close welcome modal if it's open const modal = document.getElementById('welcome-modal'); if (modal) modal.style.display = 'none'; this._started = true; this._completed = new Set(); this._activeIndex = 0; this._tries = {}; this._saveState(); localStorage.removeItem(this.COMPLETE_KEY); localStorage.removeItem(this.SKIP_KEY); this.terminal.print('╔══════════════════════════════════════╗', 'success'); this.terminal.print('║ 🎮 newOS4 Tutorial Started! ║', 'success'); this.terminal.print('╚══════════════════════════════════════╝', 'success'); this.terminal.print('Follow the quests below. Type the correct command to advance.', 'info'); this._showQuestNext(this._getQuests()[0]); } skipTutorial() { localStorage.setItem(this.SKIP_KEY, '1'); this._started = false; this._completed = new Set(); this._activeIndex = 0; this._tries = {}; localStorage.removeItem(this.STORAGE_KEY); this.terminal.print('Tutorial skipped. Type `tutorial start` to begin anytime, or `help` to see all commands.', 'info'); } /** Show status of tutorial progress */ showStatus() { const quests = this._getQuests(); const total = quests.length; const done = this._completed.size; if (!this._started && !this.isComplete()) { this.terminal.print('📋 Tutorial has not been started. Type `tutorial start` to begin!', 'info'); return; } if (this.isComplete()) { this.terminal.print('🎉 Tutorial complete! All ' + total + ' quests finished.', 'success'); return; } this.terminal.print('📋 Tutorial Progress: ' + done + '/' + total + ' quests', 'info'); quests.forEach((q, i) => { const status = this._completed.has(q.id) ? '✅' : (i === this._activeIndex ? '👉' : '⬜'); this.terminal.print(' ' + status + ' Quest ' + q.id + ': ' + q.title); }); if (this._activeIndex < total) { this.terminal.print(''); this._showQuestNext(quests[this._activeIndex]); } } // ── Internal ── _handleTutorial(args) { if (!args || args.length === 0) { // No args — show status or start if first time if (!this._started && !this.isComplete()) { this.terminal.print('╔══════════════════════════════════════╗', 'info'); this.terminal.print('║ 🎮 newOS4 Interactive Tutorial ║', 'info'); this.terminal.print('╚══════════════════════════════════════╝', 'info'); this.terminal.print('Learn newOS4 by completing 6 short quests.', 'info'); this.terminal.print(''); this.terminal.print('Commands:', 'info'); this.terminal.print(' tutorial start — Begin or restart the tutorial'); this.terminal.print(' tutorial status — Show your progress'); this.terminal.print(' tutorial skip — Dismiss the tutorial'); return; } this.showStatus(); return; } const sub = args[0].toLowerCase(); if (sub === 'start') this.startTutorial(); else if (sub === 'skip') this.skipTutorial(); else if (sub === 'status') this.showStatus(); else this.terminal.print('Usage: tutorial [start|skip|status]', 'warning'); } _showQuestNext(quest) { this.terminal.print('── Quest ' + quest.id + '/' + this._getQuests().length + ': ' + quest.title + ' ──', 'info'); this.terminal.print(' ' + quest.desc, 'text-muted'); this.terminal.print(' 💡 ' + quest.hint, 'warning'); } _showCompletion() { // Guard: only fire once if (this._completionFired) return; this._completionFired = true; this.terminal.print(''); this.terminal.print('🎉🎉🎉 CONGRATULATIONS! 🎉🎉🎉', 'success'); this.terminal.print('═'.repeat(40), 'success'); this.terminal.print(' All 6 quests complete!', 'success'); this.terminal.print(' You now know the basics of newOS4.', 'success'); this.terminal.print('═'.repeat(40), 'success'); this.terminal.print(''); this.terminal.print('What next?', 'info'); this.terminal.print(' • Explore commands: help', 'info'); this.terminal.print(' • Full docs: help.html', 'info'); this.terminal.print(' • AI assistant: ai \"your question\"', 'info'); this.terminal.print(' • Schedule tasks: cron-every 60 echo hello', 'info'); this.terminal.print(' • Install packages: wpm list', 'info'); this.terminal.print(' • Manage files: edit, find, grep, files', 'info'); localStorage.setItem(this.COMPLETE_KEY, Date.now().toString()); localStorage.removeItem(this.STORAGE_KEY); // Fire confetti this._fireConfetti(); } _fireConfetti() { const emojis = ['🎉', '✨', '⭐', '🌟', '💫']; const container = document.body; if (!container) return; // Remove any previous confetti first document.querySelectorAll('.confetti-piece').forEach(el => el.remove()); for (let i = 0; i < 15; i++) { const el = document.createElement('div'); el.className = 'confetti-piece'; el.textContent = emojis[i % emojis.length]; el.style.left = (Math.random() * 100) + 'vw'; el.style.animationDuration = (2 + Math.random() * 2) + 's'; el.style.animationDelay = (Math.random() * 1.5) + 's'; el.style.fontSize = (16 + Math.random() * 20) + 'px'; container.appendChild(el); setTimeout(() => el.remove(), 5000); } } }