Spaces:
No application file
No application file
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI-Powered Spelling Tutor</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> | |
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.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: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%); | |
| color: #fff; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; | |
| overflow: hidden; | |
| height: 100vh; | |
| } | |
| #canvasContainer { | |
| width: 100%; | |
| height: 100%; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| z-index: 1; | |
| } | |
| canvas { | |
| display: block; | |
| outline: none; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* === MAIN INTERFACE === */ | |
| .main-interface { | |
| position: fixed; | |
| top: 20px; | |
| left: 20px; | |
| right: 20px; | |
| bottom: 20px; | |
| background: rgba(20, 20, 35, 0.7); | |
| backdrop-filter: blur(10px); | |
| border-radius: 24px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4); | |
| display: flex; | |
| overflow: hidden; | |
| z-index: 2; | |
| } | |
| /* === SIDEBAR === */ | |
| .sidebar { | |
| width: 320px; | |
| background: rgba(15, 15, 25, 0.9); | |
| border-right: 1px solid rgba(255, 255, 255, 0.05); | |
| padding: 25px; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| } | |
| .sidebar-header { | |
| margin-bottom: 30px; | |
| } | |
| .sidebar-title { | |
| font-size: 1.5em; | |
| font-weight: 600; | |
| color: #5a6cff; | |
| margin-bottom: 5px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .sidebar-subtitle { | |
| color: #8892b0; | |
| font-size: 0.9em; | |
| } | |
| .section { | |
| margin-bottom: 30px; | |
| } | |
| .section-title { | |
| font-size: 1em; | |
| font-weight: 600; | |
| color: #a0b0ff; | |
| margin-bottom: 15px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .btn-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .btn { | |
| padding: 12px 20px; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 12px; | |
| color: #e0e0ff; | |
| font-family: inherit; | |
| font-size: 0.95em; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| text-align: left; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .btn:hover { | |
| background: rgba(255, 255, 255, 0.12); | |
| transform: translateX(5px); | |
| } | |
| .btn.active { | |
| background: linear-gradient(135deg, #5a6cff, #7a8aff); | |
| color: white; | |
| border-color: #5a6cff; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #5a6cff, #7a8aff); | |
| color: white; | |
| border: none; | |
| font-weight: 500; | |
| justify-content: center; | |
| } | |
| .btn-primary:hover { | |
| background: linear-gradient(135deg, #7a8aff, #5a6cff); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(90, 108, 255, 0.4); | |
| } | |
| .btn-danger { | |
| background: linear-gradient(135deg, #ff5a5a, #ff7a7a); | |
| color: white; | |
| border: none; | |
| font-weight: 500; | |
| justify-content: center; | |
| } | |
| .btn-danger:hover { | |
| background: linear-gradient(135deg, #ff7a7a, #ff5a5a); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(255, 90, 90, 0.4); | |
| } | |
| .progress-container { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 12px; | |
| padding: 15px; | |
| margin-top: 15px; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| margin: 8px 0; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, #5a6cff, #34d399); | |
| border-radius: 3px; | |
| transition: width 0.5s ease; | |
| } | |
| .progress-stats { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 0.9em; | |
| color: #8892b0; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| .stat-card { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 10px; | |
| padding: 12px; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.3em; | |
| font-weight: 600; | |
| color: #5a6cff; | |
| margin-bottom: 5px; | |
| } | |
| .stat-label { | |
| font-size: 0.8em; | |
| color: #8892b0; | |
| } | |
| /* === MAIN CONTENT === */ | |
| .main-content { | |
| flex: 1; | |
| padding: 30px; | |
| display: flex; | |
| flex-direction: column; | |
| overflow-y: auto; | |
| } | |
| .word-display-container { | |
| background: rgba(15, 15, 25, 0.8); | |
| border-radius: 20px; | |
| padding: 40px; | |
| text-align: center; | |
| margin-bottom: 30px; | |
| border: 2px solid rgba(90, 108, 255, 0.3); | |
| min-height: 200px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-direction: column; | |
| } | |
| .word-display { | |
| font-family: 'Courier New', monospace; | |
| font-size: 4em; | |
| font-weight: 800; | |
| letter-spacing: 8px; | |
| margin: 20px 0; | |
| color: white; | |
| text-shadow: 0 0 20px rgba(90, 108, 255, 0.3); | |
| } | |
| .word-display.correct { | |
| color: #34d399; | |
| text-shadow: 0 0 20px rgba(52, 211, 153, 0.3); | |
| } | |
| .word-display.incorrect { | |
| color: #ff5a5a; | |
| text-shadow: 0 0 20px rgba(255, 90, 90, 0.3); | |
| } | |
| .word-hint { | |
| color: #a0b0ff; | |
| font-size: 1.2em; | |
| margin-top: 15px; | |
| font-style: italic; | |
| } | |
| .controls-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 15px; | |
| margin-bottom: 30px; | |
| } | |
| .input-container { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| padding: 25px; | |
| margin-bottom: 25px; | |
| } | |
| .input-label { | |
| display: block; | |
| margin-bottom: 15px; | |
| color: #a0b0ff; | |
| font-weight: 500; | |
| } | |
| .answer-input { | |
| width: 100%; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 2px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 12px; | |
| padding: 18px; | |
| font-size: 1.3em; | |
| color: white; | |
| font-family: inherit; | |
| transition: all 0.3s ease; | |
| } | |
| .answer-input:focus { | |
| outline: none; | |
| border-color: #5a6cff; | |
| background: rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 0 0 3px rgba(90, 108, 255, 0.2); | |
| } | |
| .action-buttons { | |
| display: flex; | |
| gap: 15px; | |
| margin-top: 20px; | |
| } | |
| .action-btn { | |
| flex: 1; | |
| padding: 16px; | |
| border-radius: 12px; | |
| border: none; | |
| font-size: 1.1em; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 10px; | |
| } | |
| .action-btn.check { | |
| background: linear-gradient(135deg, #34d399, #10b981); | |
| color: white; | |
| } | |
| .action-btn.check:hover { | |
| background: linear-gradient(135deg, #10b981, #34d399); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(16, 185, 129, 0.4); | |
| } | |
| .action-btn.next { | |
| background: linear-gradient(135deg, #5a6cff, #7a8aff); | |
| color: white; | |
| } | |
| .action-btn.next:hover { | |
| background: linear-gradient(135deg, #7a8aff, #5a6cff); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(90, 108, 255, 0.4); | |
| } | |
| .action-btn.hint { | |
| background: linear-gradient(135deg, #fbbf24, #f59e0b); | |
| color: white; | |
| } | |
| .action-btn.hint:hover { | |
| background: linear-gradient(135deg, #f59e0b, #fbbf24); | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(251, 191, 36, 0.4); | |
| } | |
| .feedback-container { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| padding: 25px; | |
| margin-top: 25px; | |
| } | |
| .feedback-title { | |
| color: #a0b0ff; | |
| margin-bottom: 15px; | |
| font-weight: 500; | |
| } | |
| .feedback-content { | |
| color: #e0e0ff; | |
| line-height: 1.6; | |
| font-size: 1.1em; | |
| } | |
| .feedback-content.correct { | |
| color: #34d399; | |
| } | |
| .feedback-content.incorrect { | |
| color: #ff5a5a; | |
| } | |
| /* === AI CHAT === */ | |
| .ai-chat { | |
| width: 350px; | |
| background: rgba(15, 15, 25, 0.9); | |
| border-left: 1px solid rgba(255, 255, 255, 0.05); | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .chat-header { | |
| padding: 25px; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .chat-title { | |
| font-size: 1.2em; | |
| font-weight: 600; | |
| color: #5a6cff; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .chat-status { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| font-size: 0.9em; | |
| color: #8892b0; | |
| margin-top: 5px; | |
| } | |
| .status-indicator { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background: #00ff9d; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .chat-messages { | |
| flex: 1; | |
| padding: 20px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| .message { | |
| max-width: 85%; | |
| padding: 12px 16px; | |
| border-radius: 18px; | |
| line-height: 1.4; | |
| animation: messageAppear 0.3s ease; | |
| } | |
| @keyframes messageAppear { | |
| from { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .message-ai { | |
| align-self: flex-start; | |
| background: rgba(90, 108, 255, 0.2); | |
| color: #e0e0ff; | |
| border: 1px solid rgba(90, 108, 255, 0.3); | |
| } | |
| .message-user { | |
| align-self: flex-end; | |
| background: rgba(52, 211, 153, 0.2); | |
| color: #e0e0ff; | |
| border: 1px solid rgba(52, 211, 153, 0.3); | |
| } | |
| .typing-indicator { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 12px 16px; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 18px; | |
| align-self: flex-start; | |
| width: 70px; | |
| } | |
| .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; | |
| border-top: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .chat-input-wrapper { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 12px; | |
| padding: 12px 16px; | |
| color: white; | |
| font-family: inherit; | |
| resize: none; | |
| min-height: 50px; | |
| max-height: 100px; | |
| } | |
| .chat-input:focus { | |
| outline: none; | |
| border-color: #5a6cff; | |
| } | |
| .voice-input-btn { | |
| width: 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; | |
| } | |
| .voice-input-btn:hover { | |
| background: linear-gradient(135deg, #7a8aff, #5a6cff); | |
| transform: translateY(-2px); | |
| } | |
| .voice-input-btn.listening { | |
| background: linear-gradient(135deg, #ff5a5a, #ff7a7a); | |
| animation: pulse 1.5s infinite; | |
| } | |
| /* === LOADING SCREEN === */ | |
| .loading-screen { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(135deg, #0a0a0f 0%, #1a1a2e 100%); | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 1000; | |
| } | |
| .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); } | |
| } | |
| /* === RESPONSIVE === */ | |
| @media (max-width: 1200px) { | |
| .ai-chat { | |
| display: none; | |
| } | |
| } | |
| @media (max-width: 900px) { | |
| .sidebar { | |
| width: 280px; | |
| } | |
| .word-display { | |
| font-size: 3em; | |
| letter-spacing: 6px; | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| .main-interface { | |
| flex-direction: column; | |
| } | |
| .sidebar { | |
| width: 100%; | |
| border-right: none; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| max-height: 200px; | |
| } | |
| .controls-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .action-buttons { | |
| flex-direction: column; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- 3D Visualization Canvas --> | |
| <div id="canvasContainer"> | |
| <canvas id="mainCanvas"></canvas> | |
| </div> | |
| <!-- Main Interface --> | |
| <div class="main-interface"> | |
| <!-- Sidebar --> | |
| <div class="sidebar"> | |
| <div class="sidebar-header"> | |
| <div class="sidebar-title"> | |
| <i class="fas fa-brain"></i> | |
| AI Spelling Tutor | |
| </div> | |
| <div class="sidebar-subtitle">Adaptive Learning System</div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title"> | |
| <i class="fas fa-book"></i> | |
| Difficulty Level | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn active" data-level="beginner"> | |
| <i class="fas fa-seedling"></i> | |
| Beginner | |
| </button> | |
| <button class="btn" data-level="intermediate"> | |
| <i class="fas fa-leaf"></i> | |
| Intermediate | |
| </button> | |
| <button class="btn" data-level="advanced"> | |
| <i class="fas fa-tree"></i> | |
| Advanced | |
| </button> | |
| <button class="btn" data-level="expert"> | |
| <i class="fas fa-mountain"></i> | |
| Expert | |
| </button> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title"> | |
| <i class="fas fa-gamepad"></i> | |
| Training Mode | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn active" data-mode="adaptive"> | |
| <i class="fas fa-robot"></i> | |
| Adaptive AI | |
| </button> | |
| <button class="btn" data-mode="practice"> | |
| <i class="fas fa-dumbbell"></i> | |
| Practice Mode | |
| </button> | |
| <button class="btn" data-mode="challenge"> | |
| <i class="fas fa-trophy"></i> | |
| Challenge Mode | |
| </button> | |
| <button class="btn" data-mode="time-trial"> | |
| <i class="fas fa-clock"></i> | |
| Time Trial | |
| </button> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title"> | |
| <i class="fas fa-chart-line"></i> | |
| Progress | |
| </div> | |
| <div class="progress-container"> | |
| <div class="progress-stats"> | |
| <span>Level 2</span> | |
| <span>65%</span> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill" style="width: 65%"></div> | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="wordsMastered">24</div> | |
| <div class="stat-label">Words Mastered</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="streakCount">5</div> | |
| <div class="stat-label">Day Streak</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <div class="section-title"> | |
| <i class="fas fa-volume-up"></i> | |
| Voice Settings | |
| </div> | |
| <div class="btn-group"> | |
| <button class="btn active" data-voice="en-US-GuyNeural"> | |
| <i class="fas fa-user"></i> | |
| US Male | |
| </button> | |
| <button class="btn" data-voice="en-US-AriaNeural"> | |
| <i class="fas fa-user"></i> | |
| US Female | |
| </button> | |
| <button class="btn" data-voice="en-GB-SoniaNeural"> | |
| <i class="fas fa-user"></i> | |
| UK Female | |
| </button> | |
| </div> | |
| </div> | |
| <button class="btn btn-primary" id="startSession"> | |
| <i class="fas fa-play"></i> | |
| Start Training Session | |
| </button> | |
| <button class="btn btn-danger" id="resetSession" style="margin-top: 15px;"> | |
| <i class="fas fa-redo"></i> | |
| Reset Session | |
| </button> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="main-content"> | |
| <div class="word-display-container"> | |
| <div class="word-display" id="currentWord">Ready to begin?</div> | |
| <div class="word-hint" id="wordHint"></div> | |
| </div> | |
| <div class="input-container"> | |
| <label class="input-label"> | |
| <i class="fas fa-keyboard"></i> | |
| Type your spelling here: | |
| </label> | |
| <input type="text" class="answer-input" id="answerInput" placeholder="Type the word you hear..."> | |
| <div class="action-buttons"> | |
| <button class="action-btn check" id="checkButton"> | |
| <i class="fas fa-check"></i> | |
| Check Answer | |
| </button> | |
| <button class="action-btn hint" id="hintButton"> | |
| <i class="fas fa-lightbulb"></i> | |
| Get Hint | |
| </button> | |
| <button class="action-btn next" id="nextButton" style="display: none;"> | |
| <i class="fas fa-arrow-right"></i> | |
| Next Word | |
| </button> | |
| </div> | |
| </div> | |
| <div class="controls-grid"> | |
| <button class="btn" id="hearButton"> | |
| <i class="fas fa-volume-up"></i> | |
| Hear Word | |
| </button> | |
| <button class="btn" id="spellButton"> | |
| <i class="fas fa-spell-check"></i> | |
| Spell Out | |
| </button> | |
| <button class="btn" id="contextButton"> | |
| <i class="fas fa-comment-alt"></i> | |
| Context Sentence | |
| </button> | |
| <button class="btn" id="repeatButton"> | |
| <i class="fas fa-redo"></i> | |
| Repeat | |
| </button> | |
| </div> | |
| <div class="feedback-container"> | |
| <div class="feedback-title"> | |
| <i class="fas fa-comment-dots"></i> | |
| AI Tutor Feedback | |
| </div> | |
| <div class="feedback-content" id="aiFeedback"> | |
| Welcome to AI Spelling Tutor! I'll help you improve your spelling skills through personalized training. Click "Start Training Session" to begin! | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Chat --> | |
| <div class="ai-chat"> | |
| <div class="chat-header"> | |
| <div class="chat-title"> | |
| <i class="fas fa-robot"></i> | |
| AI Tutor | |
| </div> | |
| <div class="chat-status"> | |
| <span class="status-indicator"></span> | |
| <span>Adaptive Learning Active</span> | |
| </div> | |
| </div> | |
| <div class="chat-messages" id="chatMessages"> | |
| <div class="message message-ai"> | |
| Hello! I'm your AI spelling tutor. I'll adapt to your skill level and help you master spelling through personalized exercises. | |
| </div> | |
| <div class="message message-ai"> | |
| Click "Start Training Session" to begin. I'll track your progress and adjust difficulty based on your performance. | |
| </div> | |
| <div class="typing-indicator" id="typingIndicator" style="display: none;"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| </div> | |
| </div> | |
| <div class="chat-input-container"> | |
| <div class="chat-input-wrapper"> | |
| <textarea class="chat-input" id="chatInput" placeholder="Ask your AI tutor a question..."></textarea> | |
| <button class="voice-input-btn" id="voiceButton"> | |
| <i class="fas fa-microphone"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading Screen --> | |
| <div class="loading-screen" id="loadingScreen"> | |
| <div class="spinner"></div> | |
| <div id="loadingText">Initializing AI Tutor System...</div> | |
| </div> | |
| <script> | |
| // ============================================================================ | |
| // CORE AI SPELLING TUTOR SYSTEM | |
| // ============================================================================ | |
| class AISpellingTutor { | |
| constructor() { | |
| this.initializeState(); | |
| this.initialize3D(); | |
| this.setupEventListeners(); | |
| this.hideLoading(); | |
| } | |
| initializeState() { | |
| // Core training state | |
| this.state = { | |
| sessionActive: false, | |
| currentWord: null, | |
| currentWordIndex: 0, | |
| attempts: 0, | |
| hintsUsed: 0, | |
| isCorrect: false, | |
| showNextButton: false, | |
| // Adaptive learning metrics | |
| difficulty: 'beginner', | |
| trainingMode: 'adaptive', | |
| currentVoice: 'en-US-GuyNeural', | |
| // Progress tracking | |
| wordsMastered: 24, | |
| currentStreak: 5, | |
| accuracy: 85, | |
| levelProgress: 65, | |
| // Word banks by difficulty | |
| wordLists: { | |
| beginner: [ | |
| { word: "cat", context: "The small ___ purred softly.", phonetic: "/kæt/" }, | |
| { word: "dog", context: "The loyal ___ wagged its tail.", phonetic: "/dɒɡ/" }, | |
| { word: "house", context: "We live in a big red ___.", phonetic: "/haʊs/" }, | |
| { word: "book", context: "She read an interesting ___.", phonetic: "/bʊk/" }, | |
| { word: "friend", context: "My best ___ is coming over.", phonetic: "/frɛnd/" }, | |
| { word: "water", context: "I drink eight glasses of ___ daily.", phonetic: "/ˈwɔːtər/" }, | |
| { word: "happy", context: "She felt very ___ today.", phonetic: "/ˈhæpi/" }, | |
| { word: "school", context: "Children learn at ___.", phonetic: "/skuːl/" } | |
| ], | |
| intermediate: [ | |
| { word: "beautiful", context: "The sunset was incredibly ___.", phonetic: "/ˈbjuːtɪfʊl/" }, | |
| { word: "chocolate", context: "She loves dark ___.", phonetic: "/ˈtʃɒklət/" }, | |
| { word: "mountain", context: "We climbed the high ___.", phonetic: "/ˈmaʊntɪn/" }, | |
| { word: "language", context: "English is a global ___.", phonetic: "/ˈlæŋɡwɪdʒ/" }, | |
| { word: "calendar", context: "Check the ___ for dates.", phonetic: "/ˈkælɪndər/" }, | |
| { word: "restaurant", context: "We dined at a fancy ___.", phonetic: "/ˈrɛstrɒnt/" }, | |
| { word: "environment", context: "Protect the natural ___.", phonetic: "/ɪnˈvaɪrənmənt/" }, | |
| { word: "temperature", context: "The ___ is rising today.", phonetic: "/ˈtɛmprətʃər/" } | |
| ], | |
| advanced: [ | |
| { word: "pneumonia", context: "He was hospitalized with ___.", phonetic: "/njuːˈmoʊniə/" }, | |
| { word: "xylophone", context: "She played the ___ beautifully.", phonetic: "/ˈzaɪləfoʊn/" }, | |
| { word: "bouquet", context: "He brought her a ___ of roses.", phonetic: "/buːˈkeɪ/" }, | |
| { word: "mnemonic", context: "A ___ helps with memory.", phonetic: "/nɪˈmɒnɪk/" }, | |
| { word: "chrysanthemum", context: "The ___ bloomed in autumn.", phonetic: "/krɪˈsænθəməm/" }, | |
| { word: "psychology", context: "She studies ___.", phonetic: "/saɪˈkɒlədʒi/" }, | |
| { word: "rendezvous", context: "Their ___ was at sunset.", phonetic: "/ˈrɒndeɪvuː/" }, | |
| { word: "entrepreneur", context: "The ___ started a business.", phonetic: "/ˌɒntrəprəˈnɜːr/" } | |
| ], | |
| expert: [ | |
| { word: "onomatopoeia", context: "Buzz is an example of ___.", phonetic: "/ˌɒnəˌmætəˈpiːə/" }, | |
| { word: "synecdoche", context: "All hands on deck is a ___.", phonetic: "/sɪˈnɛkdəki/" }, | |
| { word: "antediluvian", context: "The artifact was ___.", phonetic: "/ˌæntɪdɪˈluːviən/" }, | |
| { word: "sesquipedalian", context: "He used ___ words.", phonetic: "/ˌsɛskwɪpɪˈdeɪliən/" }, | |
| { word: "perspicacious", context: "Her insights were ___.", phonetic: "/ˌpɜːspɪˈkeɪʃəs/" }, | |
| { word: "serendipity", context: "Finding the book was pure ___.", phonetic: "/ˌsɛrənˈdɪpɪti/" }, | |
| { word: "philanthropist", context: "The ___ donated millions.", phonetic: "/fɪˈlænθrəpɪst/" }, | |
| { word: "antidisestablishmentarianism", context: "He studied ___ movements.", phonetic: "/ˌæntidɪsəˌstæblɪʃmənˈtɛəriənɪzəm/" } | |
| ] | |
| }, | |
| // Current word list based on difficulty | |
| currentList: [], | |
| // Performance tracking | |
| sessionStats: { | |
| correct: 0, | |
| incorrect: 0, | |
| totalAttempts: 0, | |
| totalHints: 0, | |
| startTime: null, | |
| sessionDuration: 0 | |
| } | |
| }; | |
| // Speech synthesis | |
| this.synth = window.speechSynthesis; | |
| this.voices = []; | |
| this.currentUtterance = null; | |
| // Initialize voices | |
| this.initializeVoices(); | |
| } | |
| initializeVoices() { | |
| const loadVoices = () => { | |
| this.voices = this.synth.getVoices(); | |
| console.log('Voices loaded:', this.voices.length); | |
| }; | |
| if (this.synth.onvoiceschanged !== undefined) { | |
| this.synth.onvoiceschanged = loadVoices; | |
| } | |
| if (this.synth.getVoices().length > 0) { | |
| loadVoices(); | |
| } | |
| } | |
| async initialize3D() { | |
| try { | |
| // Check if Three.js is available | |
| if (typeof THREE === 'undefined') { | |
| console.log('Three.js not available, skipping 3D'); | |
| return; | |
| } | |
| // Create scene | |
| this.scene = new THREE.Scene(); | |
| this.scene.background = new THREE.Color(0x0a0a0f); | |
| // Create camera | |
| this.camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| this.camera.position.z = 5; | |
| // Create 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 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); | |
| // Add particles | |
| this.createParticles(); | |
| // Start animation loop | |
| this.animate(); | |
| // Handle window resize | |
| window.addEventListener('resize', () => this.onWindowResize()); | |
| console.log('3D scene initialized'); | |
| } catch (error) { | |
| console.error('3D initialization error:', error); | |
| } | |
| } | |
| createParticles() { | |
| const particleCount = 1000; | |
| const geometry = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(particleCount * 3); | |
| for (let i = 0; i < particleCount; i++) { | |
| const i3 = i * 3; | |
| positions[i3] = (Math.random() - 0.5) * 10; | |
| positions[i3 + 1] = (Math.random() - 0.5) * 10; | |
| positions[i3 + 2] = (Math.random() - 0.5) * 10; | |
| } | |
| geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| const material = new THREE.PointsMaterial({ | |
| size: 0.05, | |
| color: 0x5a6cff, | |
| transparent: true, | |
| opacity: 0.6, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| this.particles = new THREE.Points(geometry, material); | |
| this.scene.add(this.particles); | |
| // Add central sphere | |
| const sphereGeometry = new THREE.SphereGeometry(1, 32, 32); | |
| const sphereMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x5a6cff, | |
| wireframe: true, | |
| transparent: true, | |
| opacity: 0.1 | |
| }); | |
| this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial); | |
| this.scene.add(this.sphere); | |
| } | |
| animate() { | |
| requestAnimationFrame(() => this.animate()); | |
| if (this.particles) { | |
| this.particles.rotation.y += 0.001; | |
| this.particles.rotation.x += 0.0005; | |
| // Pulsing effect | |
| const time = Date.now() * 0.001; | |
| const scale = 1 + Math.sin(time) * 0.1; | |
| this.particles.scale.set(scale, scale, scale); | |
| } | |
| if (this.sphere) { | |
| this.sphere.rotation.y += 0.002; | |
| } | |
| if (this.renderer && this.scene && this.camera) { | |
| this.renderer.render(this.scene, this.camera); | |
| } | |
| } | |
| onWindowResize() { | |
| if (this.camera && this.renderer) { | |
| this.camera.aspect = window.innerWidth / window.innerHeight; | |
| this.camera.updateProjectionMatrix(); | |
| this.renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| } | |
| setupEventListeners() { | |
| // Start session button | |
| document.getElementById('startSession').addEventListener('click', () => this.startSession()); | |
| // Reset session button | |
| document.getElementById('resetSession').addEventListener('click', () => this.resetSession()); | |
| // Check answer button | |
| document.getElementById('checkButton').addEventListener('click', () => this.checkAnswer()); | |
| // Next word button | |
| document.getElementById('nextButton').addEventListener('click', () => this.nextWord()); | |
| // Hint button | |
| document.getElementById('hintButton').addEventListener('click', () => this.giveHint()); | |
| // Audio buttons | |
| document.getElementById('hearButton').addEventListener('click', () => this.speakWord()); | |
| document.getElementById('spellButton').addEventListener('click', () => this.spellWord()); | |
| document.getElementById('contextButton').addEventListener('click', () => this.speakContext()); | |
| document.getElementById('repeatButton').addEventListener('click', () => this.repeatWord()); | |
| // Answer input - handle Enter key | |
| document.getElementById('answerInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| this.checkAnswer(); | |
| } | |
| }); | |
| // Difficulty buttons | |
| document.querySelectorAll('[data-level]').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| this.setDifficulty(e.target.dataset.level); | |
| }); | |
| }); | |
| // Training mode buttons | |
| document.querySelectorAll('[data-mode]').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| this.setTrainingMode(e.target.dataset.mode); | |
| }); | |
| }); | |
| // Voice buttons | |
| document.querySelectorAll('[data-voice]').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| this.setVoice(e.target.dataset.voice); | |
| }); | |
| }); | |
| // Chat input | |
| document.getElementById('chatInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| this.sendChatMessage(); | |
| } | |
| }); | |
| // Voice input button | |
| document.getElementById('voiceButton').addEventListener('click', () => this.toggleVoiceInput()); | |
| } | |
| hideLoading() { | |
| setTimeout(() => { | |
| document.getElementById('loadingScreen').style.opacity = '0'; | |
| setTimeout(() => { | |
| document.getElementById('loadingScreen').style.display = 'none'; | |
| }, 500); | |
| }, 1500); | |
| } | |
| // ============================================================================ | |
| // SESSION MANAGEMENT | |
| // ============================================================================ | |
| startSession() { | |
| this.state.sessionActive = true; | |
| this.state.sessionStats.startTime = Date.now(); | |
| this.state.currentWordIndex = 0; | |
| this.state.currentList = [...this.state.wordLists[this.state.difficulty]]; | |
| // Shuffle the list for practice | |
| if (this.state.trainingMode === 'practice' || this.state.trainingMode === 'challenge') { | |
| this.shuffleArray(this.state.currentList); | |
| } | |
| // Reset session stats | |
| this.state.sessionStats = { | |
| correct: 0, | |
| incorrect: 0, | |
| totalAttempts: 0, | |
| totalHints: 0, | |
| startTime: Date.now(), | |
| sessionDuration: 0 | |
| }; | |
| // Update UI | |
| document.getElementById('startSession').disabled = true; | |
| document.getElementById('startSession').innerHTML = '<i class="fas fa-spinner fa-spin"></i> Session Active'; | |
| // Get first word | |
| this.getNewWord(); | |
| // Add AI chat message | |
| this.addAIMessage(`Starting ${this.state.difficulty} level spelling session! I'll adapt to your performance.`); | |
| this.showTypingIndicator(); | |
| setTimeout(() => { | |
| this.hideTypingIndicator(); | |
| this.addAIMessage(`First word: "${this.state.currentWord.word}". Listen carefully and type what you hear.`); | |
| }, 1500); | |
| } | |
| resetSession() { | |
| if (confirm('Are you sure you want to reset the current session?')) { | |
| this.state.sessionActive = false; | |
| this.state.currentWord = null; | |
| this.state.currentWordIndex = 0; | |
| this.state.attempts = 0; | |
| this.state.hintsUsed = 0; | |
| this.state.isCorrect = false; | |
| this.state.showNextButton = false; | |
| // Reset UI | |
| document.getElementById('currentWord').textContent = 'Ready to begin?'; | |
| document.getElementById('currentWord').className = 'word-display'; | |
| document.getElementById('wordHint').textContent = ''; | |
| document.getElementById('answerInput').value = ''; | |
| document.getElementById('aiFeedback').textContent = 'Session reset. Click "Start Training Session" to begin!'; | |
| document.getElementById('aiFeedback').className = 'feedback-content'; | |
| document.getElementById('nextButton').style.display = 'none'; | |
| document.getElementById('checkButton').style.display = 'flex'; | |
| document.getElementById('hintButton').style.display = 'flex'; | |
| document.getElementById('startSession').disabled = false; | |
| document.getElementById('startSession').innerHTML = '<i class="fas fa-play"></i> Start Training Session'; | |
| // Clear chat messages | |
| document.getElementById('chatMessages').innerHTML = ` | |
| <div class="message message-ai"> | |
| Hello! I'm your AI spelling tutor. I'll adapt to your skill level and help you master spelling through personalized exercises. | |
| </div> | |
| <div class="message message-ai"> | |
| Click "Start Training Session" to begin. I'll track your progress and adjust difficulty based on your performance. | |
| </div> | |
| <div class="typing-indicator" id="typingIndicator" style="display: none;"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| </div> | |
| `; | |
| this.addAIMessage('Session reset. Ready for a new challenge?'); | |
| } | |
| } | |
| getNewWord() { | |
| if (this.state.currentWordIndex < this.state.currentList.length) { | |
| this.state.currentWord = this.state.currentList[this.state.currentWordIndex]; | |
| this.state.attempts = 0; | |
| this.state.hintsUsed = 0; | |
| this.state.isCorrect = false; | |
| this.state.showNextButton = false; | |
| // Update UI | |
| document.getElementById('currentWord').textContent = '_'.repeat(this.state.currentWord.word.length); | |
| document.getElementById('currentWord').className = 'word-display'; | |
| document.getElementById('wordHint').textContent = ''; | |
| document.getElementById('answerInput').value = ''; | |
| document.getElementById('answerInput').focus(); | |
| document.getElementById('nextButton').style.display = 'none'; | |
| document.getElementById('checkButton').style.display = 'flex'; | |
| document.getElementById('hintButton').style.display = 'flex'; | |
| // Speak the word | |
| setTimeout(() => this.speakWord(), 500); | |
| // Update feedback | |
| document.getElementById('aiFeedback').textContent = `New word! Listen carefully and spell it correctly. Difficulty: ${this.state.difficulty}`; | |
| return true; | |
| } else { | |
| this.endSession(); | |
| return false; | |
| } | |
| } | |
| nextWord() { | |
| this.state.currentWordIndex++; | |
| this.getNewWord(); | |
| } | |
| endSession() { | |
| this.state.sessionActive = false; | |
| this.state.sessionStats.sessionDuration = Date.now() - this.state.sessionStats.startTime; | |
| const accuracy = this.state.sessionStats.totalAttempts > 0 | |
| ? Math.round((this.state.sessionStats.correct / this.state.sessionStats.totalAttempts) * 100) | |
| : 0; | |
| // Update progress | |
| if (accuracy > 80) { | |
| this.state.levelProgress = Math.min(100, this.state.levelProgress + 10); | |
| document.getElementById('progressFill').style.width = `${this.state.levelProgress}%`; | |
| } | |
| // Update UI | |
| document.getElementById('currentWord').textContent = 'Session Complete!'; | |
| document.getElementById('wordHint').textContent = `Accuracy: ${accuracy}% | Words: ${this.state.sessionStats.correct}`; | |
| document.getElementById('answerInput').value = ''; | |
| document.getElementById('aiFeedback').textContent = `Great job! You completed the session with ${accuracy}% accuracy. Ready for another challenge?`; | |
| document.getElementById('nextButton').style.display = 'none'; | |
| document.getElementById('startSession').disabled = false; | |
| document.getElementById('startSession').innerHTML = '<i class="fas fa-play"></i> Start Training Session'; | |
| // Add AI message | |
| this.addAIMessage(`Session completed! You got ${this.state.sessionStats.correct} words correct with ${accuracy}% accuracy.`); | |
| // Update stats | |
| this.updateStats(); | |
| } | |
| // ============================================================================ | |
| // SPELLING LOGIC | |
| // ============================================================================ | |
| checkAnswer() { | |
| const userInput = document.getElementById('answerInput').value.trim(); | |
| if (!userInput) return; | |
| this.state.attempts++; | |
| this.state.sessionStats.totalAttempts++; | |
| const isCorrect = userInput.toLowerCase() === this.state.currentWord.word.toLowerCase(); | |
| if (isCorrect) { | |
| this.handleCorrectAnswer(); | |
| } else { | |
| this.handleIncorrectAnswer(userInput); | |
| } | |
| } | |
| handleCorrectAnswer() { | |
| this.state.isCorrect = true; | |
| this.state.showNextButton = true; | |
| this.state.sessionStats.correct++; | |
| this.state.wordsMastered++; | |
| // Update UI | |
| document.getElementById('currentWord').textContent = this.state.currentWord.word; | |
| document.getElementById('currentWord').className = 'word-display correct'; | |
| document.getElementById('answerInput').value = ''; | |
| document.getElementById('nextButton').style.display = 'flex'; | |
| document.getElementById('checkButton').style.display = 'none'; | |
| document.getElementById('hintButton').style.display = 'none'; | |
| // Calculate points | |
| const basePoints = 10; | |
| const hintPenalty = this.state.hintsUsed * 2; | |
| const attemptPenalty = Math.max(0, this.state.attempts - 1) * 3; | |
| const pointsEarned = Math.max(1, basePoints - hintPenalty - attemptPenalty); | |
| // Update feedback | |
| document.getElementById('aiFeedback').textContent = `✅ Excellent! "${this.state.currentWord.word}" is correct! `; | |
| document.getElementById('aiFeedback').className = 'feedback-content correct'; | |
| if (this.state.attempts === 1 && this.state.hintsUsed === 0) { | |
| document.getElementById('aiFeedback').textContent += `Perfect first try! +${pointsEarned} points!`; | |
| } else { | |
| document.getElementById('aiFeedback').textContent += `You used ${this.state.hintsUsed} hint(s) and ${this.state.attempts} attempt(s). +${pointsEarned} points!`; | |
| } | |
| // Add AI chat message | |
| this.addAIMessage(`Correct! "${this.state.currentWord.word}" is spelled correctly. Great job!`); | |
| // Play success sound | |
| this.playSuccessSound(); | |
| // Update stats | |
| this.updateStats(); | |
| // Adaptive learning: adjust difficulty based on performance | |
| if (this.state.attempts === 1 && this.state.hintsUsed === 0) { | |
| this.considerDifficultyIncrease(); | |
| } | |
| } | |
| handleIncorrectAnswer(userInput) { | |
| this.state.sessionStats.incorrect++; | |
| // Show incorrect feedback | |
| document.getElementById('currentWord').className = 'word-display incorrect'; | |
| document.getElementById('aiFeedback').textContent = `❌ Not quite. "${userInput}" is incorrect. Try again!`; | |
| document.getElementById('aiFeedback').className = 'feedback-content incorrect'; | |
| // Add AI chat message | |
| this.addAIMessage(`Incorrect attempt for "${this.state.currentWord.word}". Try again or ask for a hint!`); | |
| // Give progressive hints | |
| if (this.state.attempts === 2) { | |
| this.giveHint(); | |
| } else if (this.state.attempts === 3) { | |
| this.revealFirstLetter(); | |
| } | |
| // Adaptive learning: consider difficulty decrease if struggling | |
| if (this.state.attempts >= 3) { | |
| this.considerDifficultyDecrease(); | |
| } | |
| } | |
| giveHint() { | |
| if (!this.state.currentWord || this.state.isCorrect) return; | |
| this.state.hintsUsed++; | |
| this.state.sessionStats.totalHints++; | |
| // Show phonetic hint | |
| if (this.state.hintsUsed === 1) { | |
| document.getElementById('wordHint').textContent = `Phonetic: ${this.state.currentWord.phonetic}`; | |
| this.addAIMessage(`Hint: The word sounds like "${this.state.currentWord.phonetic}"`); | |
| } | |
| // Show context hint | |
| else if (this.state.hintsUsed === 2) { | |
| document.getElementById('wordHint').textContent += ` | Context: ${this.state.currentWord.context}`; | |
| this.addAIMessage(`Context: ${this.state.currentWord.context}`); | |
| } | |
| // Show first letter | |
| else if (this.state.hintsUsed === 3) { | |
| this.revealFirstLetter(); | |
| } | |
| } | |
| revealFirstLetter() { | |
| if (!this.state.currentWord) return; | |
| const word = this.state.currentWord.word; | |
| const displayed = document.getElementById('currentWord').textContent; | |
| const firstLetter = word[0]; | |
| // Update display to show first letter | |
| let newDisplay = firstLetter; | |
| for (let i = 1; i < word.length; i++) { | |
| newDisplay += displayed[i] === '_' ? '_' : displayed[i]; | |
| } | |
| document.getElementById('currentWord').textContent = newDisplay; | |
| this.addAIMessage(`First letter revealed: "${firstLetter}"`); | |
| } | |
| // ============================================================================ | |
| // SPEECH SYNTHESIS | |
| // ============================================================================ | |
| speakText(text, rate = 1.0, pitch = 1.0) { | |
| if (!this.synth) return; | |
| // Stop any current speech | |
| this.synth.cancel(); | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| // Try to find the selected voice | |
| if (this.voices.length > 0) { | |
| const selectedVoice = this.voices.find(voice => | |
| voice.name.includes(this.state.currentVoice) || | |
| voice.lang.includes(this.state.currentVoice.split('-').slice(0, 2).join('-')) | |
| ); | |
| if (selectedVoice) { | |
| utterance.voice = selectedVoice; | |
| } | |
| } | |
| utterance.rate = rate; | |
| utterance.pitch = pitch; | |
| utterance.volume = 1.0; | |
| this.currentUtterance = utterance; | |
| this.synth.speak(utterance); | |
| } | |
| speakWord() { | |
| if (!this.state.currentWord) return; | |
| this.speakText(this.state.currentWord.word, 0.8, 1.0); | |
| // Add visual feedback | |
| const wordDisplay = document.getElementById('currentWord'); | |
| const originalText = wordDisplay.textContent; | |
| wordDisplay.style.color = '#5a6cff'; | |
| setTimeout(() => { | |
| wordDisplay.style.color = ''; | |
| }, 1000); | |
| } | |
| spellWord() { | |
| if (!this.state.currentWord) return; | |
| const word = this.state.currentWord.word.toUpperCase(); | |
| const letters = word.split(''); | |
| const spelled = letters.join('. ') + '.'; | |
| this.speakText(spelled, 0.5, 1.1); | |
| // Visual spelling display | |
| let display = ''; | |
| let i = 0; | |
| const spellInterval = setInterval(() => { | |
| if (i < word.length) { | |
| display = word.substring(0, i + 1) + '_'.repeat(word.length - i - 1); | |
| document.getElementById('currentWord').textContent = display; | |
| i++; | |
| } else { | |
| clearInterval(spellInterval); | |
| setTimeout(() => { | |
| document.getElementById('currentWord').textContent = '_'.repeat(word.length); | |
| }, 1000); | |
| } | |
| }, 500); | |
| } | |
| speakContext() { | |
| if (!this.state.currentWord || !this.state.currentWord.context) return; | |
| this.speakText(this.state.currentWord.context, 0.9, 1.0); | |
| // Highlight context in feedback | |
| document.getElementById('aiFeedback').textContent = `Context: ${this.state.currentWord.context}`; | |
| setTimeout(() => { | |
| if (!this.state.isCorrect) { | |
| document.getElementById('aiFeedback').textContent = 'Try spelling the word from the context clue!'; | |
| } | |
| }, 3000); | |
| } | |
| repeatWord() { | |
| this.speakWord(); | |
| } | |
| playSuccessSound() { | |
| // Create a simple success tone using Web Audio API | |
| try { | |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContext.destination); | |
| oscillator.frequency.setValueAtTime(523.25, audioContext.currentTime); // C5 | |
| oscillator.frequency.setValueAtTime(659.25, audioContext.currentTime + 0.1); // E5 | |
| oscillator.frequency.setValueAtTime(783.99, audioContext.currentTime + 0.2); // G5 | |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5); | |
| oscillator.start(audioContext.currentTime); | |
| oscillator.stop(audioContext.currentTime + 0.5); | |
| } catch (e) { | |
| console.log('Web Audio API not supported'); | |
| } | |
| } | |
| // ============================================================================ | |
| // ADAPTIVE LEARNING AI | |
| // ============================================================================ | |
| considerDifficultyIncrease() { | |
| // Check if user has perfect streak | |
| const perfectStreak = this.getPerfectStreak(); | |
| if (perfectStreak >= 3) { | |
| const currentLevel = this.state.difficulty; | |
| const nextLevel = this.getNextDifficultyLevel(currentLevel); | |
| if (nextLevel) { | |
| this.addAIMessage(`🎯 Perfect streak detected! Advancing to ${nextLevel} level for next session.`); | |
| this.state.difficulty = nextLevel; | |
| // Update UI | |
| document.querySelectorAll('[data-level]').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.level === nextLevel); | |
| }); | |
| } | |
| } | |
| } | |
| considerDifficultyDecrease() { | |
| // Check if user is struggling | |
| const recentAccuracy = this.getRecentAccuracy(); | |
| if (recentAccuracy < 50) { | |
| const currentLevel = this.state.difficulty; | |
| const prevLevel = this.getPreviousDifficultyLevel(currentLevel); | |
| if (prevLevel) { | |
| this.addAIMessage(`I notice you're finding this challenging. Let's try ${prevLevel} level for better learning.`); | |
| this.state.difficulty = prevLevel; | |
| // Update UI | |
| document.querySelectorAll('[data-level]').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.level === prevLevel); | |
| }); | |
| } | |
| } | |
| } | |
| getPerfectStreak() { | |
| // Simplified - in real implementation, track actual streak | |
| return this.state.sessionStats.correct; | |
| } | |
| getRecentAccuracy() { | |
| if (this.state.sessionStats.totalAttempts === 0) return 100; | |
| return (this.state.sessionStats.correct / this.state.sessionStats.totalAttempts) * 100; | |
| } | |
| getNextDifficultyLevel(current) { | |
| const levels = ['beginner', 'intermediate', 'advanced', 'expert']; | |
| const currentIndex = levels.indexOf(current); | |
| return currentIndex < levels.length - 1 ? levels[currentIndex + 1] : null; | |
| } | |
| getPreviousDifficultyLevel(current) { | |
| const levels = ['beginner', 'intermediate', 'advanced', 'expert']; | |
| const currentIndex = levels.indexOf(current); | |
| return currentIndex > 0 ? levels[currentIndex - 1] : null; | |
| } | |
| // ============================================================================ | |
| // CHAT AI FUNCTIONALITY | |
| // ============================================================================ | |
| addAIMessage(text) { | |
| const chatMessages = document.getElementById('chatMessages'); | |
| const typingIndicator = document.getElementById('typingIndicator'); | |
| if (typingIndicator.style.display !== 'none') { | |
| typingIndicator.style.display = 'none'; | |
| } | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = 'message message-ai'; | |
| messageDiv.textContent = text; | |
| chatMessages.appendChild(messageDiv); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| addUserMessage(text) { | |
| const chatMessages = document.getElementById('chatMessages'); | |
| const typingIndicator = document.getElementById('typingIndicator'); | |
| if (typingIndicator.style.display !== 'none') { | |
| typingIndicator.style.display = 'none'; | |
| } | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = 'message message-user'; | |
| messageDiv.textContent = text; | |
| chatMessages.appendChild(messageDiv); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| showTypingIndicator() { | |
| const typingIndicator = document.getElementById('typingIndicator'); | |
| typingIndicator.style.display = 'flex'; | |
| const chatMessages = document.getElementById('chatMessages'); | |
| chatMessages.scrollTop = chatMessages.scrollHeight; | |
| } | |
| hideTypingIndicator() { | |
| const typingIndicator = document.getElementById('typingIndicator'); | |
| typingIndicator.style.display = 'none'; | |
| } | |
| sendChatMessage() { | |
| const chatInput = document.getElementById('chatInput'); | |
| const message = chatInput.value.trim(); | |
| if (!message) return; | |
| // Add user message | |
| this.addUserMessage(message); | |
| // Clear input | |
| chatInput.value = ''; | |
| // Show typing indicator | |
| this.showTypingIndicator(); | |
| // AI response with delay | |
| setTimeout(() => { | |
| this.hideTypingIndicator(); | |
| this.generateAIResponse(message); | |
| }, 1000 + Math.random() * 1000); | |
| } | |
| generateAIResponse(userMessage) { | |
| const lowerMessage = userMessage.toLowerCase(); | |
| let response = ''; | |
| // Simple AI response logic - in production, connect to actual AI API | |
| if (lowerMessage.includes('help') || lowerMessage.includes('how')) { | |
| response = "I'm here to help you improve your spelling! Listen to the word, type it correctly, and I'll give you feedback. Use hints if you need help!"; | |
| } else if (lowerMessage.includes('difficulty') || lowerMessage.includes('hard') || lowerMessage.includes('easy')) { | |
| response = "I automatically adjust difficulty based on your performance. Get words correct on the first try to advance levels!"; | |
| } else if (lowerMessage.includes('score') || lowerMessage.includes('points')) { | |
| response = `Your current stats: ${this.state.wordsMastered} words mastered, ${this.state.currentStreak} day streak, Level ${Math.floor(this.state.levelProgress/25)+1}.`; | |
| } else if (lowerMessage.includes('hint') || lowerMessage.includes('clue')) { | |
| response = "Click the 'Get Hint' button for phonetic clues, context sentences, or letter reveals. Don't overuse hints for maximum points!"; | |
| } else if (lowerMessage.includes('current word') || lowerMessage.includes('what word')) { | |
| if (this.state.currentWord) { | |
| response = `The current word is "${this.state.currentWord.word}". Listen carefully and try to spell it!`; | |
| } else { | |
| response = "Start a training session to get your first word!"; | |
| } | |
| } else if (lowerMessage.includes('thank')) { | |
| response = "You're welcome! Keep practicing to become a spelling champion! 🏆"; | |
| } else { | |
| response = this.getRandomAIResponse(); | |
| } | |
| this.addAIMessage(response); | |
| // Optional: Speak the response | |
| if (this.state.sessionActive) { | |
| this.speakText(response, 0.9, 1.0); | |
| } | |
| } | |
| getRandomAIResponse() { | |
| const responses = [ | |
| "Great question! Focus on listening carefully to each word. The phonetic clues can help if you're stuck.", | |
| "Remember, spelling improves with practice. Each word you master builds your vocabulary!", | |
| "I'm tracking your progress and will adjust the difficulty to match your skill level.", | |
| "Try to spell words without hints for maximum points. But don't hesitate to use hints if you need them!", | |
| "Pay attention to vowel sounds and silent letters - they're often the trickiest parts of spelling.", | |
| "Consistent practice is key to spelling mastery. Even 10 minutes a day makes a big difference!" | |
| ]; | |
| return responses[Math.floor(Math.random() * responses.length)]; | |
| } | |
| toggleVoiceInput() { | |
| const voiceBtn = document.getElementById('voiceButton'); | |
| const isListening = voiceBtn.classList.contains('listening'); | |
| if (!isListening) { | |
| // Start listening | |
| voiceBtn.classList.add('listening'); | |
| voiceBtn.innerHTML = '<i class="fas fa-stop"></i>'; | |
| this.startVoiceRecognition(); | |
| } else { | |
| // Stop listening | |
| voiceBtn.classList.remove('listening'); | |
| voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>'; | |
| this.stopVoiceRecognition(); | |
| } | |
| } | |
| startVoiceRecognition() { | |
| if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) { | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| this.recognition = new SpeechRecognition(); | |
| this.recognition.continuous = false; | |
| this.recognition.interimResults = false; | |
| this.recognition.lang = 'en-US'; | |
| this.recognition.onresult = (event) => { | |
| const transcript = event.results[0][0].transcript; | |
| document.getElementById('answerInput').value = transcript; | |
| // Auto-check if in spelling mode | |
| if (this.state.sessionActive && !this.state.isCorrect) { | |
| setTimeout(() => this.checkAnswer(), 500); | |
| } | |
| }; | |
| this.recognition.onerror = (event) => { | |
| console.log('Speech recognition error:', event.error); | |
| }; | |
| this.recognition.start(); | |
| } else { | |
| alert('Speech recognition not supported in this browser. Try Chrome or Edge.'); | |
| } | |
| } | |
| stopVoiceRecognition() { | |
| if (this.recognition) { | |
| this.recognition.stop(); | |
| } | |
| } | |
| // ============================================================================ | |
| // UI SETTINGS | |
| // ============================================================================ | |
| setDifficulty(level) { | |
| this.state.difficulty = level; | |
| // Update UI | |
| document.querySelectorAll('[data-level]').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.level === level); | |
| }); | |
| this.addAIMessage(`Difficulty set to ${level} level. Words will match this skill level.`); | |
| } | |
| setTrainingMode(mode) { | |
| this.state.trainingMode = mode; | |
| // Update UI | |
| document.querySelectorAll('[data-mode]').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.mode === mode); | |
| }); | |
| const modeDescriptions = { | |
| 'adaptive': 'AI adapts difficulty in real-time', | |
| 'practice': 'Random words for general practice', | |
| 'challenge': 'Time-limited spelling challenges', | |
| 'time-trial': 'Race against the clock' | |
| }; | |
| this.addAIMessage(`Training mode set to ${mode}. ${modeDescriptions[mode]}`); | |
| } | |
| setVoice(voice) { | |
| this.state.currentVoice = voice; | |
| // Update UI | |
| document.querySelectorAll('[data-voice]').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.voice === voice); | |
| }); | |
| // Test the voice | |
| this.speakText('Voice updated', 0.9, 1.0); | |
| } | |
| updateStats() { | |
| document.getElementById('wordsMastered').textContent = this.state.wordsMastered; | |
| document.getElementById('streakCount').textContent = this.state.currentStreak; | |
| // Update progress bar | |
| document.getElementById('progressFill').style.width = `${this.state.levelProgress}%`; | |
| // Update sidebar progress | |
| const progressContainer = document.querySelector('.progress-stats'); | |
| const level = Math.floor(this.state.levelProgress / 25) + 1; | |
| progressContainer.innerHTML = `<span>Level ${level}</span><span>${this.state.levelProgress}%</span>`; | |
| } | |
| // ============================================================================ | |
| // UTILITY FUNCTIONS | |
| // ============================================================================ | |
| shuffleArray(array) { | |
| for (let i = array.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [array[i], array[j]] = [array[j], array[i]]; | |
| } | |
| return array; | |
| } | |
| } | |
| // ============================================================================ | |
| // INITIALIZE APPLICATION | |
| // ============================================================================ | |
| // Start the AI Spelling Tutor when page loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| window.spellingTutor = new AISpellingTutor(); | |
| // Add keyboard shortcuts | |
| document.addEventListener('keydown', (e) => { | |
| // Ctrl + Space to hear word | |
| if (e.ctrlKey && e.code === 'Space') { | |
| e.preventDefault(); | |
| window.spellingTutor.speakWord(); | |
| } | |
| // Ctrl + Enter to check answer | |
| if (e.ctrlKey && e.key === 'Enter') { | |
| e.preventDefault(); | |
| window.spellingTutor.checkAnswer(); | |
| } | |
| // Ctrl + H for hint | |
| if (e.ctrlKey && e.key === 'h') { | |
| e.preventDefault(); | |
| window.spellingTutor.giveHint(); | |
| } | |
| // Ctrl + N for next word (when available) | |
| if (e.ctrlKey && e.key === 'n') { | |
| e.preventDefault(); | |
| if (window.spellingTutor.state.showNextButton) { | |
| window.spellingTutor.nextWord(); | |
| } | |
| } | |
| }); | |
| // Instructions tooltip | |
| setTimeout(() => { | |
| if (window.spellingTutor) { | |
| window.spellingTutor.addAIMessage('💡 Tip: Use Ctrl+Space to hear the word, Ctrl+Enter to check your answer!'); | |
| } | |
| }, 3000); | |
| }); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| if (window.spellingTutor && window.spellingTutor.onWindowResize) { | |
| window.spellingTutor.onWindowResize(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |