/** * TextModule - Reading comprehension exercises with AI validation * Handles text passages with comprehension questions and contextual understanding */ import ExerciseModuleInterface from '../interfaces/ExerciseModuleInterface.js'; class TextModule extends ExerciseModuleInterface { constructor(orchestrator, llmValidator, prerequisiteEngine, contextMemory) { super(); // Validate dependencies if (!orchestrator || !llmValidator || !prerequisiteEngine || !contextMemory) { throw new Error('TextModule requires all service dependencies'); } this.orchestrator = orchestrator; this.llmValidator = llmValidator; this.prerequisiteEngine = prerequisiteEngine; this.contextMemory = contextMemory; // Module state this.initialized = false; this.container = null; this.currentExerciseData = null; this.currentText = null; this.currentQuestion = null; this.questionIndex = 0; this.questionResults = []; this.validationInProgress = false; this.lastValidationResult = null; this.aiAvailable = false; // Configuration this.config = { requiredProvider: 'openai', // Prefer OpenAI for text analysis model: 'gpt-4o-mini', temperature: 0.2, // Slightly more creative for text comprehension maxTokens: 800, timeout: 45000, // Longer timeout for complex text analysis questionsPerText: 3, // Default number of questions per text showTextDuringQuestions: true, // Keep text visible during questions allowReread: true // Allow re-reading the text }; // Languages configuration this.languages = { userLanguage: 'English', targetLanguage: 'French' }; // Bind methods this._handleUserInput = this._handleUserInput.bind(this); this._handleNextQuestion = this._handleNextQuestion.bind(this); this._handleRetry = this._handleRetry.bind(this); this._handleRereadText = this._handleRereadText.bind(this); } async init() { if (this.initialized) return; console.log('๐Ÿ“– Initializing TextModule...'); // Test AI connectivity - recommended for text comprehension try { const testResult = await this.llmValidator.testConnectivity(); if (testResult.success) { console.log(`โœ… AI connectivity verified for text analysis (providers: ${testResult.availableProviders?.join(', ') || testResult.provider})`); this.aiAvailable = true; } else { console.warn('โš ๏ธ AI connection failed - text comprehension will be limited:', testResult.error); this.aiAvailable = false; } } catch (error) { console.warn('โš ๏ธ AI connectivity test failed - using basic text analysis:', error.message); this.aiAvailable = false; } this.initialized = true; console.log(`โœ… TextModule initialized (AI: ${this.aiAvailable ? 'available for deep analysis' : 'limited - basic analysis only'})`); } /** * Check if module can run with current prerequisites * @param {Array} prerequisites - List of learned vocabulary/concepts * @param {Object} chapterContent - Full chapter content * @returns {boolean} - True if module can run */ canRun(prerequisites, chapterContent) { // Check if there are texts and if prerequisites allow them const texts = chapterContent?.texts || []; if (texts.length === 0) return false; // Find texts that can be unlocked with current prerequisites const availableTexts = texts.filter(text => { const unlockStatus = this.prerequisiteEngine.canUnlock('text', text); return unlockStatus.canUnlock; }); return availableTexts.length > 0; } /** * Present exercise UI and content * @param {HTMLElement} container - DOM container to render into * @param {Object} exerciseData - Specific exercise data to present * @returns {Promise} */ async present(container, exerciseData) { if (!this.initialized) { throw new Error('TextModule must be initialized before use'); } this.container = container; this.currentExerciseData = exerciseData; this.currentText = exerciseData.text; this.questionIndex = 0; this.questionResults = []; this.validationInProgress = false; this.lastValidationResult = null; // Detect languages from chapter content this._detectLanguages(exerciseData); // Generate or extract questions this.questions = await this._prepareQuestions(this.currentText); console.log(`๐Ÿ“– Presenting text comprehension: "${this.currentText.title || 'Reading Exercise'}" (${this.questions.length} questions)`); // Render initial UI await this._renderTextExercise(); // Start with text reading phase this._showTextReading(); } /** * Validate user input with AI for deep text comprehension * @param {string} userInput - User's response * @param {Object} context - Exercise context * @returns {Promise} - Validation result with score and feedback */ async validate(userInput, context) { if (!userInput || !userInput.trim()) { throw new Error('Please provide an answer'); } if (!this.currentText || !this.currentQuestion) { throw new Error('No text or question loaded for validation'); } console.log(`๐Ÿ“– Validating text comprehension answer for question ${this.questionIndex + 1}`); // Build comprehensive prompt for text comprehension const prompt = this._buildTextComprehensionPrompt(userInput); try { // Use AI validation with structured response const aiResponse = await this.llmValidator.iaEngine.validateEducationalContent(prompt, { preferredProvider: this.config.requiredProvider, temperature: this.config.temperature, maxTokens: this.config.maxTokens, timeout: this.config.timeout, systemPrompt: `You are an expert reading comprehension evaluator. Focus on understanding and critical thinking, not just literal recall. ALWAYS respond in the exact format: [answer]yes/no [explanation]your detailed analysis here` }); // Parse structured response const parsedResult = this._parseStructuredResponse(aiResponse); // Record interaction in context memory this.contextMemory.recordInteraction({ type: 'text', subtype: 'comprehension', content: { text: this.currentText, question: this.currentQuestion, textTitle: this.currentText.title || 'Reading Exercise', textLength: this.currentText.content?.length || 0 }, userResponse: userInput.trim(), validation: parsedResult, context: { languages: this.languages, questionIndex: this.questionIndex, totalQuestions: this.questions.length } }); return parsedResult; } catch (error) { console.error('โŒ AI text comprehension validation failed:', error); // Fallback to basic keyword analysis if AI fails if (!this.aiAvailable) { return this._performBasicTextValidation(userInput); } throw new Error(`Text comprehension validation failed: ${error.message}. Please check your answer and try again.`); } } /** * Get current progress data * @returns {ProgressData} - Progress information for this module */ getProgress() { const totalQuestions = this.questions ? this.questions.length : 0; const completedQuestions = this.questionResults.length; const correctAnswers = this.questionResults.filter(result => result.correct).length; return { type: 'text', textTitle: this.currentText?.title || 'Reading Exercise', totalQuestions, completedQuestions, correctAnswers, currentQuestionIndex: this.questionIndex, questionResults: this.questionResults, progressPercentage: totalQuestions > 0 ? Math.round((completedQuestions / totalQuestions) * 100) : 0, comprehensionRate: completedQuestions > 0 ? Math.round((correctAnswers / completedQuestions) * 100) : 0, aiAnalysisAvailable: this.aiAvailable }; } /** * Clean up and prepare for unloading */ cleanup() { console.log('๐Ÿงน Cleaning up TextModule...'); // Remove event listeners if (this.container) { this.container.innerHTML = ''; } // Reset state this.container = null; this.currentExerciseData = null; this.currentText = null; this.currentQuestion = null; this.questionIndex = 0; this.questionResults = []; this.questions = null; this.validationInProgress = false; this.lastValidationResult = null; console.log('โœ… TextModule cleaned up'); } /** * Get module metadata * @returns {Object} - Module information */ getMetadata() { return { name: 'TextModule', type: 'text', version: '1.0.0', description: 'Reading comprehension exercises with AI-powered text analysis', capabilities: ['text_comprehension', 'critical_thinking', 'contextual_analysis', 'ai_feedback'], aiRequired: false, // Can work without AI but limited config: this.config }; } // Private Methods /** * Detect languages from exercise data * @private */ _detectLanguages(exerciseData) { const chapterContent = this.currentExerciseData?.chapterContent; if (chapterContent?.metadata?.userLanguage) { this.languages.userLanguage = chapterContent.metadata.userLanguage; } if (chapterContent?.metadata?.targetLanguage) { this.languages.targetLanguage = chapterContent.metadata.targetLanguage; } console.log(`๐ŸŒ Text languages detected: ${this.languages.userLanguage} -> ${this.languages.targetLanguage}`); } /** * Prepare questions for the text * @private */ async _prepareQuestions(text) { // If text already has questions, use them if (text.questions && text.questions.length > 0) { return text.questions.map((q, index) => ({ id: `q${index + 1}`, question: q.question || q.text || q, type: q.type || 'open', expectedAnswer: q.answer || q.expectedAnswer, keywords: q.keywords || [], difficulty: q.difficulty || 'medium' })); } // Generate default comprehension questions const defaultQuestions = [ { id: 'main_idea', question: `What is the main idea of this text?`, type: 'open', keywords: ['main', 'central', 'primary', 'key'], difficulty: 'medium' }, { id: 'details', question: `What are the key details mentioned in the text?`, type: 'open', keywords: ['details', 'specific', 'mentioned'], difficulty: 'easy' }, { id: 'analysis', question: `What can you infer or conclude from this text?`, type: 'open', keywords: ['infer', 'conclude', 'imply', 'suggest'], difficulty: 'hard' } ]; // Limit to configured number of questions return defaultQuestions.slice(0, this.config.questionsPerText); } /** * Build comprehensive prompt for text comprehension validation * @private */ _buildTextComprehensionPrompt(userAnswer) { const textContent = this.currentText.content || this.currentText.text || ''; const textTitle = this.currentText.title || 'Reading Text'; return `You are evaluating reading comprehension for a language learning exercise. CRITICAL: You MUST respond in this EXACT format: [answer]yes/no [explanation]your detailed analysis here TEXT PASSAGE: Title: "${textTitle}" Content: "${textContent}" QUESTION: ${this.currentQuestion.question} STUDENT RESPONSE: "${userAnswer}" EVALUATION CONTEXT: - Exercise Type: Reading comprehension - Languages: ${this.languages.userLanguage} -> ${this.languages.targetLanguage} - Question Type: ${this.currentQuestion.type} - Question Difficulty: ${this.currentQuestion.difficulty} - Question ${this.questionIndex + 1} of ${this.questions.length} EVALUATION CRITERIA: - [answer]yes if the student demonstrates understanding of the text in relation to the question - [answer]no if the response shows lack of comprehension or is unrelated - Focus on COMPREHENSION and UNDERSTANDING, not perfect language - Accept paraphrasing and different perspectives if they show understanding - Consider cultural context and language learning level [explanation] should provide: 1. What the student understood correctly 2. What they might have missed or misunderstood 3. Encouragement and specific improvement suggestions 4. Connection to the broader text meaning Format: [answer]yes/no [explanation]your comprehensive educational feedback here`; } /** * Parse structured AI response for text comprehension * @private */ _parseStructuredResponse(aiResponse) { try { let responseText = ''; // Extract text from AI response if (typeof aiResponse === 'string') { responseText = aiResponse; } else if (aiResponse.content) { responseText = aiResponse.content; } else if (aiResponse.text) { responseText = aiResponse.text; } else { responseText = JSON.stringify(aiResponse); } console.log('๐Ÿ” Parsing AI text comprehension response:', responseText.substring(0, 150) + '...'); // Extract [answer] - case insensitive const answerMatch = responseText.match(/\[answer\](yes|no)/i); if (!answerMatch) { throw new Error('AI response missing [answer] format'); } // Extract [explanation] - multiline support const explanationMatch = responseText.match(/\[explanation\](.+)/s); if (!explanationMatch) { throw new Error('AI response missing [explanation] format'); } const isCorrect = answerMatch[1].toLowerCase() === 'yes'; const explanation = explanationMatch[1].trim(); // Higher scores for text comprehension to encourage reading const result = { score: isCorrect ? 90 : 60, // More generous scoring for comprehension correct: isCorrect, feedback: explanation, answer: answerMatch[1].toLowerCase(), explanation: explanation, timestamp: new Date().toISOString(), provider: this.config.requiredProvider, model: this.config.model, cached: false, formatValid: true, textComprehension: true }; console.log(`โœ… AI text comprehension parsed: ${result.answer} - Score: ${result.score}`); return result; } catch (error) { console.error('โŒ Failed to parse AI text comprehension response:', error); console.error('Raw response:', aiResponse); throw new Error(`AI response format invalid: ${error.message}`); } } /** * Perform basic text validation when AI is unavailable * @private */ _performBasicTextValidation(userAnswer) { console.log('๐Ÿ” Performing basic text validation (AI unavailable)'); const answerLength = userAnswer.trim().length; const hasKeywords = this.currentQuestion.keywords?.some(keyword => userAnswer.toLowerCase().includes(keyword.toLowerCase()) ); // Basic scoring based on answer length and keyword presence let score = 40; // Base score if (answerLength > 20) score += 20; // Substantial answer if (answerLength > 50) score += 10; // Detailed answer if (hasKeywords) score += 20; // Contains relevant keywords if (answerLength > 100) score += 10; // Very detailed const isCorrect = score >= 70; return { score: Math.min(score, 100), correct: isCorrect, feedback: isCorrect ? "Good comprehension demonstrated! Your answer shows understanding of the text." : "Your answer could be more detailed. Try to include more specific information from the text.", timestamp: new Date().toISOString(), provider: 'basic_text_analysis', model: 'keyword_length_analysis', cached: false, mockGenerated: true, textComprehension: true }; } /** * Render the text exercise interface * @private */ async _renderTextExercise() { if (!this.container || !this.currentText) return; const textTitle = this.currentText.title || 'Reading Exercise'; const textContent = this.currentText.content || this.currentText.text || ''; const wordCount = textContent.split(/\s+/).length; this.container.innerHTML = `

๐Ÿ“– Reading Comprehension

${this.questions?.length || 0} questions โ€ข ~${wordCount} words ${!this.aiAvailable ? ' โ€ข โš ๏ธ Limited analysis mode' : ' โ€ข ๐Ÿง  AI analysis'}

${textTitle}

${this._formatTextContent(textContent)}
`; // Add CSS styles this._addStyles(); // Add event listeners this._setupEventListeners(); } /** * Format text content with paragraphs and line breaks * @private */ _formatTextContent(content) { if (!content) return ''; // Split into paragraphs and format return content .split('\n\n') .map(paragraph => `

${paragraph.replace(/\n/g, '
')}

`) .join(''); } /** * Setup event listeners for text exercise * @private */ _setupEventListeners() { const startQuestionsBtn = document.getElementById('start-questions-btn'); const rereadBtn = document.getElementById('reread-btn'); const answerInput = document.getElementById('answer-input'); const validateBtn = document.getElementById('validate-answer-btn'); const retryBtn = document.getElementById('retry-answer-btn'); const nextBtn = document.getElementById('next-question-btn'); const finishBtn = document.getElementById('finish-text-btn'); // Start questions button if (startQuestionsBtn) { startQuestionsBtn.onclick = () => this._startQuestions(); } // Re-read text button if (rereadBtn) { rereadBtn.onclick = this._handleRereadText; } // Answer input validation if (answerInput) { answerInput.addEventListener('input', () => { const hasText = answerInput.value.trim().length > 0; if (validateBtn) { validateBtn.disabled = !hasText || this.validationInProgress; } }); // Allow Ctrl+Enter to validate answerInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && !validateBtn.disabled) { e.preventDefault(); this._handleUserInput(); } }); } // Validate button if (validateBtn) { validateBtn.onclick = this._handleUserInput; } // Action buttons if (retryBtn) retryBtn.onclick = this._handleRetry; if (nextBtn) nextBtn.onclick = this._handleNextQuestion; if (finishBtn) finishBtn.onclick = () => this._completeTextExercise(); } /** * Show text reading phase * @private */ _showTextReading() { const textSection = document.getElementById('text-passage-section'); const questionsSection = document.getElementById('questions-section'); if (textSection) textSection.style.display = 'block'; if (questionsSection) questionsSection.style.display = 'none'; } /** * Start questions phase * @private */ _startQuestions() { const textSection = document.getElementById('text-passage-section'); const questionsSection = document.getElementById('questions-section'); const rereadBtn = document.getElementById('reread-btn'); if (textSection) { textSection.style.display = this.config.showTextDuringQuestions ? 'block' : 'none'; } if (questionsSection) questionsSection.style.display = 'block'; if (rereadBtn) rereadBtn.style.display = 'inline-block'; this._presentCurrentQuestion(); } /** * Present current question * @private */ _presentCurrentQuestion() { if (this.questionIndex >= this.questions.length) { this._showTextResults(); return; } this.currentQuestion = this.questions[this.questionIndex]; const questionContent = document.getElementById('question-content'); const questionCounter = document.getElementById('question-counter'); const progressFill = document.getElementById('progress-fill'); if (!questionContent || !this.currentQuestion) return; // Update progress const progressPercentage = ((this.questionIndex + 1) / this.questions.length) * 100; if (progressFill) progressFill.style.width = `${progressPercentage}%`; if (questionCounter) questionCounter.textContent = `Question ${this.questionIndex + 1} of ${this.questions.length}`; // Display question questionContent.innerHTML = `
${this.currentQuestion.question}
${this.currentQuestion.type} ${this.currentQuestion.difficulty}
`; // Clear previous answer and focus const answerInput = document.getElementById('answer-input'); if (answerInput) { answerInput.value = ''; answerInput.focus(); } // Hide explanation panel const explanationPanel = document.getElementById('explanation-panel'); if (explanationPanel) explanationPanel.style.display = 'none'; } /** * Handle user input validation * @private */ async _handleUserInput() { const answerInput = document.getElementById('answer-input'); const validateBtn = document.getElementById('validate-answer-btn'); const statusDiv = document.getElementById('validation-status'); if (!answerInput || !validateBtn || !statusDiv) return; const userAnswer = answerInput.value.trim(); if (!userAnswer) return; try { // Set validation in progress this.validationInProgress = true; validateBtn.disabled = true; answerInput.disabled = true; // Show loading status statusDiv.innerHTML = `
๐Ÿ”
${this.aiAvailable ? 'AI is analyzing your comprehension...' : 'Analyzing your answer...'}
`; // Call validation const result = await this.validate(userAnswer, {}); this.lastValidationResult = result; // Store result this.questionResults[this.questionIndex] = { question: this.currentQuestion.question, userAnswer: userAnswer, correct: result.correct, score: result.score, feedback: result.feedback, timestamp: new Date().toISOString() }; // Show result this._showValidationResult(result); // Update status statusDiv.innerHTML = `
${result.correct ? 'โœ…' : '๐Ÿ“š'} Analysis complete
`; } catch (error) { console.error('โŒ Text validation error:', error); // Show error status statusDiv.innerHTML = `
โš ๏ธ Error: ${error.message}
`; // Re-enable input for retry this._enableRetry(); } } /** * Show validation result in explanation panel * @private */ _showValidationResult(result) { const explanationPanel = document.getElementById('explanation-panel'); const explanationContent = document.getElementById('explanation-content'); const nextBtn = document.getElementById('next-question-btn'); const retryBtn = document.getElementById('retry-answer-btn'); const finishBtn = document.getElementById('finish-text-btn'); if (!explanationPanel || !explanationContent) return; // Show panel explanationPanel.style.display = 'block'; // Set explanation content explanationContent.innerHTML = `
${result.correct ? 'โœ… Good Comprehension!' : '๐Ÿ“š Keep Learning!'} Score: ${result.score}/100
${result.explanation || result.feedback}
${result.textComprehension ? '
๐Ÿ’ก This analysis focuses on your understanding of the text\'s meaning and context.
' : ''}
`; // Show appropriate buttons const isLastQuestion = this.questionIndex >= this.questions.length - 1; if (nextBtn) nextBtn.style.display = isLastQuestion ? 'none' : 'inline-block'; if (finishBtn) finishBtn.style.display = isLastQuestion ? 'inline-block' : 'none'; if (retryBtn) retryBtn.style.display = result.correct ? 'none' : 'inline-block'; // Scroll to explanation explanationPanel.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } /** * Handle next question * @private */ _handleNextQuestion() { this.questionIndex++; this._presentCurrentQuestion(); } /** * Handle retry * @private */ _handleRetry() { // Hide explanation and enable new input const explanationPanel = document.getElementById('explanation-panel'); const statusDiv = document.getElementById('validation-status'); if (explanationPanel) explanationPanel.style.display = 'none'; if (statusDiv) statusDiv.innerHTML = ''; this._enableRetry(); } /** * Handle re-read text * @private */ _handleRereadText() { const textSection = document.getElementById('text-passage-section'); if (textSection) { textSection.style.display = 'block'; textSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } /** * Enable retry after error * @private */ _enableRetry() { this.validationInProgress = false; const answerInput = document.getElementById('answer-input'); const validateBtn = document.getElementById('validate-answer-btn'); if (answerInput) { answerInput.disabled = false; answerInput.focus(); } if (validateBtn) { validateBtn.disabled = false; } } /** * Show final text results * @private */ _showTextResults() { const resultsContainer = document.getElementById('text-results'); const questionsSection = document.getElementById('questions-section'); if (!resultsContainer) return; const correctCount = this.questionResults.filter(result => result.correct).length; const totalCount = this.questionResults.length; const comprehensionRate = totalCount > 0 ? Math.round((correctCount / totalCount) * 100) : 0; let resultClass = 'results-poor'; if (comprehensionRate >= 80) resultClass = 'results-excellent'; else if (comprehensionRate >= 60) resultClass = 'results-good'; const resultsHTML = `

๐Ÿ“Š Reading Comprehension Results

${comprehensionRate}% Comprehension Rate
${correctCount} / ${totalCount} questions understood well
${this.questionResults.map((result, index) => `
Q${index + 1} ${result.correct ? 'โœ…' : '๐Ÿ“š'} Score: ${result.score}/100
`).join('')}
`; resultsContainer.innerHTML = resultsHTML; resultsContainer.style.display = 'block'; // Hide other sections if (questionsSection) questionsSection.style.display = 'none'; // Add action listeners document.getElementById('complete-text-btn').onclick = () => this._completeTextExercise(); document.getElementById('review-text-btn').onclick = () => this._reviewText(); } /** * Complete text exercise * @private */ _completeTextExercise() { // Mark text as comprehended if performance is good const correctCount = this.questionResults.filter(result => result.correct).length; const comprehensionRate = correctCount / this.questionResults.length; if (comprehensionRate >= 0.6) { // 60% comprehension threshold const textId = this.currentText.id || this.currentText.title || 'text_exercise'; const metadata = { comprehensionRate: Math.round(comprehensionRate * 100), questionsAnswered: this.questionResults.length, correctAnswers: correctCount, sessionId: this.orchestrator?.sessionId || 'unknown', moduleType: 'text', aiAnalysisUsed: this.aiAvailable }; this.prerequisiteEngine.markPhraseMastered(textId, metadata); // Also save to persistent storage if (window.addMasteredItem && this.orchestrator?.bookId && this.orchestrator?.chapterId) { window.addMasteredItem( this.orchestrator.bookId, this.orchestrator.chapterId, 'texts', textId, metadata ); } } // Emit completion event this.orchestrator._eventBus.emit('drs:exerciseCompleted', { moduleType: 'text', results: this.questionResults, progress: this.getProgress() }, 'TextModule'); } /** * Review text again * @private */ _reviewText() { this.questionIndex = 0; this.questionResults = []; this._showTextReading(); const resultsContainer = document.getElementById('text-results'); if (resultsContainer) resultsContainer.style.display = 'none'; } /** * Add CSS styles for text exercise * @private */ _addStyles() { if (document.getElementById('text-module-styles')) return; const styles = document.createElement('style'); styles.id = 'text-module-styles'; styles.textContent = ` .text-exercise { max-width: 900px; margin: 0 auto; padding: 20px; display: grid; gap: 20px; } .exercise-header { text-align: center; margin-bottom: 20px; } .text-info { margin-top: 10px; } .text-meta { color: #666; font-size: 0.9em; } .text-content { display: grid; gap: 20px; } .text-passage-card, .question-card { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); } .passage-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 2px solid #eee; } .passage-header h3 { margin: 0; color: #333; font-size: 1.5em; } .passage-content { line-height: 1.8; font-size: 1.1em; color: #444; margin-bottom: 30px; max-height: 400px; overflow-y: auto; } .passage-content p { margin-bottom: 1.2em; } .reading-actions { text-align: center; } .question-progress { margin-bottom: 25px; } .progress-indicator { text-align: center; } .progress-bar { width: 100%; height: 8px; background-color: #e0e0e0; border-radius: 4px; margin-top: 10px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(90deg, #667eea, #764ba2); transition: width 0.5s ease; } .question-display { margin-bottom: 25px; padding: 20px; background: linear-gradient(135deg, #f8f9ff, #e8f4fd); border-radius: 10px; border-left: 4px solid #667eea; } .question-text { font-size: 1.3em; font-weight: 600; color: #333; margin-bottom: 15px; line-height: 1.4; } .question-meta { display: flex; gap: 15px; align-items: center; } .question-type { background: #e3f2fd; color: #1976d2; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 500; } .difficulty-easy { background: #e8f5e8; color: #2e7d32; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 500; } .difficulty-medium { background: #fff3e0; color: #f57c00; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 500; } .difficulty-hard { background: #ffebee; color: #c62828; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 500; } .answer-input-section { margin-bottom: 20px; } .answer-input-section label { display: block; margin-bottom: 10px; font-weight: 600; color: #555; } .answer-input-section textarea { width: 100%; padding: 15px; font-size: 1.05em; border: 2px solid #ddd; border-radius: 8px; resize: vertical; min-height: 100px; box-sizing: border-box; transition: border-color 0.3s ease; font-family: inherit; line-height: 1.5; } .answer-input-section textarea:focus { outline: none; border-color: #667eea; } .question-controls { display: flex; flex-direction: column; align-items: center; gap: 15px; } .validation-status { min-height: 30px; display: flex; align-items: center; justify-content: center; } .status-loading, .status-complete, .status-error { display: flex; align-items: center; gap: 10px; padding: 12px 20px; border-radius: 25px; font-weight: 500; } .status-loading { background: #e3f2fd; color: #1976d2; } .status-complete { background: #e8f5e8; color: #2e7d32; } .status-error { background: #ffebee; color: #c62828; } .loading-spinner { animation: spin 1s linear infinite; } .explanation-panel { background: white; border-radius: 12px; padding: 25px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); border-left: 4px solid #667eea; } .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .panel-header h3 { margin: 0; color: #333; } .analysis-model { font-size: 0.9em; color: #666; background: #f5f5f5; padding: 4px 8px; border-radius: 4px; } .explanation-result { padding: 20px; border-radius: 8px; margin-bottom: 15px; } .explanation-result.correct { border-left: 4px solid #4caf50; background: linear-gradient(135deg, #f1f8e9, #e8f5e8); } .explanation-result.needs-improvement { border-left: 4px solid #ff9800; background: linear-gradient(135deg, #fff8e1, #fff3e0); } .result-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; font-weight: 600; } .result-indicator { font-size: 1.1em; } .comprehension-score { font-size: 0.9em; color: #666; } .explanation-text { line-height: 1.6; color: #333; font-size: 1.05em; margin-bottom: 10px; } .analysis-note { font-size: 0.9em; color: #666; font-style: italic; padding-top: 10px; border-top: 1px solid #eee; } .panel-actions { display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; } .text-results-content { background: white; border-radius: 12px; padding: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); text-align: center; } .results-summary { margin-bottom: 30px; } .comprehension-display { margin-bottom: 15px; } .comprehension-rate { font-size: 3em; font-weight: bold; display: block; } .results-excellent .comprehension-rate { color: #4caf50; } .results-good .comprehension-rate { color: #ff9800; } .results-poor .comprehension-rate { color: #f44336; } .comprehension-label { font-size: 1.2em; color: #666; } .questions-summary { font-size: 1.1em; color: #555; } .question-breakdown { display: grid; gap: 10px; margin-bottom: 30px; } .question-result { display: flex; align-items: center; justify-content: space-between; padding: 15px; border-radius: 8px; background: #f8f9fa; } .question-result.understood { background: linear-gradient(135deg, #e8f5e8, #f1f8e9); border-left: 4px solid #4caf50; } .question-result.needs-work { background: linear-gradient(135deg, #fff8e1, #fff3e0); border-left: 4px solid #ff9800; } .question-summary { display: flex; align-items: center; gap: 15px; } .question-num { font-weight: bold; color: #333; } .results-actions { display: flex; gap: 15px; justify-content: center; flex-wrap: wrap; } .btn { padding: 12px 24px; border: none; border-radius: 8px; cursor: pointer; font-size: 1em; font-weight: 500; transition: all 0.3s ease; display: inline-flex; align-items: center; gap: 8px; text-decoration: none; } .btn:disabled { opacity: 0.6; cursor: not-allowed; } .btn-primary { background: linear-gradient(135deg, #667eea, #764ba2); color: white; } .btn-primary:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } .btn-secondary { background: #6c757d; color: white; } .btn-secondary:hover:not(:disabled) { background: #5a6268; } .btn-outline { background: transparent; border: 2px solid #667eea; color: #667eea; } .btn-outline:hover:not(:disabled) { background: #667eea; color: white; } .btn-success { background: linear-gradient(135deg, #4caf50, #45a049); color: white; } .btn-success:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3); } .btn-sm { padding: 8px 16px; font-size: 0.9em; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @media (max-width: 768px) { .text-exercise { padding: 15px; } .text-passage-card, .question-card, .explanation-panel { padding: 20px; } .question-text { font-size: 1.2em; } .comprehension-rate { font-size: 2.5em; } .panel-actions, .results-actions { flex-direction: column; } .passage-header { flex-direction: column; gap: 15px; align-items: flex-start; } } `; document.head.appendChild(styles); } } export default TextModule;