/
SN1054NGC
/
JavaScript_traner
Обзор
Документация
Войти
/
SN1054NGC
/
JavaScript_traner
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
JavaScript_traner.html
2 412 строк
97 KB
Андрей Логвиненко
create JavaScript_traner.html
28 янв 2026, 01:55
Верифицирован
28 янв 2026, 01:55
51fa03e
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript Тренажер - Интерактивный симулятор программирования</title> <style> * { box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } body { background: linear-gradient(135deg, #0f2b3d 0%, #1a3a4f 100%); color: #e8f4fc; line-height: 1.6; margin: 0; padding: 20px; min-height: 100vh; } .container { max-width: 1600px; margin: 0 auto; display: flex; flex-direction: column; gap: 25px; } header { text-align: center; padding: 20px; border-bottom: 1px solid #2a5a7c; background-color: rgba(26, 58, 79, 0.8); border-radius: 12px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); } h1 { color: #4dabf7; margin-bottom: 10px; font-size: 2.5rem; background: linear-gradient(90deg, #4dabf7, #339af0); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .subtitle { color: #a5d8ff; font-size: 1.2rem; max-width: 800px; margin: 0 auto; } .main-content { display: flex; flex-wrap: wrap; gap: 25px; } .simulator { flex: 2; min-width: 600px; background-color: #1a3a4f; border-radius: 12px; padding: 25px; border: 1px solid #2a5a7c; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); display: flex; flex-direction: column; } .simulator-content { flex: 1; display: flex; flex-direction: column; } .js-reference { flex: 1; min-width: 400px; background-color: #1a3a4f; border-radius: 12px; padding: 25px; border: 1px solid #2a5a7c; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); } h2 { color: #4dabf7; margin-top: 0; padding-bottom: 15px; border-bottom: 1px solid #2a5a7c; display: flex; align-items: center; gap: 10px; } h2::before { content: '{'; color: #ff922b; font-weight: bold; } h2::after { content: '}'; color: #ff922b; font-weight: bold; } .workspace { display: flex; flex-direction: column; gap: 20px; margin-bottom: 20px; flex: 1; } .code-container { display: flex; flex-direction: column; gap: 20px; flex: 1; } .editor-panel { display: flex; gap: 20px; flex-wrap: wrap; } .js-editor, .console-output { flex: 1; min-width: 300px; background-color: #0f2b3d; border-radius: 8px; padding: 20px; border: 1px solid #2a5a7c; display: flex; flex-direction: column; } .editor-title { color: #4dabf7; margin-bottom: 10px; font-weight: bold; display: flex; align-items: center; gap: 8px; } .code-area { background-color: #0a1e2b; border-radius: 6px; padding: 15px; font-family: 'Fira Code', 'Cascadia Code', 'Courier New', monospace; min-height: 200px; max-height: 300px; overflow-y: auto; white-space: pre-wrap; color: #e8f4fc; line-height: 1.5; tab-size: 2; flex: 1; } .console-area { background-color: #0a1e2b; border-radius: 6px; padding: 15px; font-family: 'Fira Code', 'Cascadia Code', 'Courier New', monospace; min-height: 200px; max-height: 300px; overflow-y: auto; color: #e8f4fc; line-height: 1.5; flex: 1; } .js-keyword { color: #ff6b6b; } .js-string { color: #51cf66; } .js-number { color: #ff922b; } .js-function { color: #4dabf7; } .js-comment { color: #5c7c8a; font-style: italic; } .js-operator { color: #da77f2; } .console-line { margin-bottom: 5px; font-family: 'Fira Code', monospace; } .console-log { color: #a5d8ff; } .console-error { color: #ff6b6b; } .console-warn { color: #ff922b; } .console-info { color: #51cf66; } .preview-container { background-color: #ffffff; border-radius: 8px; padding: 20px; border: 1px solid #2a5a7c; min-height: 200px; color: #333; font-family: Arial, sans-serif; overflow: auto; } .preview-title { color: #4dabf7; margin-bottom: 15px; display: flex; align-items: center; gap: 8px; } .input-area { display: flex; gap: 15px; margin-top: 20px; } textarea { flex: 1; padding: 15px; background-color: #0a1e2b; border: 2px solid #2a5a7c; border-radius: 8px; color: #f0f6fc; font-family: 'Fira Code', 'Courier New', monospace; font-size: 1rem; transition: all 0.3s ease; min-height: 100px; resize: vertical; } textarea:focus { outline: none; border-color: #4dabf7; box-shadow: 0 0 0 3px rgba(77, 171, 247, 0.2); } textarea::placeholder { color: #5c7c8a; } button { padding: 15px 25px; background-color: #339af0; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; transition: all 0.2s ease; display: flex; align-items: center; gap: 8px; } button:hover { background-color: #4dabf7; transform: translateY(-2px); box-shadow: 0 4px 12px rgba(51, 154, 240, 0.3); } button:active { transform: translateY(0); } .hint-btn { background-color: #2a5a7c; } .hint-btn:hover { background-color: #3a6a8c; box-shadow: 0 4px 12px rgba(77, 171, 247, 0.2); } .skip-btn { background-color: #ff6b6b; } .skip-btn:hover { background-color: #ff8787; box-shadow: 0 4px 12px rgba(255, 107, 107, 0.3); } .button-group { display: flex; gap: 12px; margin-top: 20px; flex-wrap: wrap; } .method-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 18px; margin-top: 20px; max-height: 500px; overflow-y: auto; padding-right: 10px; } .method-list::-webkit-scrollbar { width: 8px; } .method-list::-webkit-scrollbar-track { background: #0a1e2b; border-radius: 4px; } .method-list::-webkit-scrollbar-thumb { background: #2a5a7c; border-radius: 4px; } .method-list::-webkit-scrollbar-thumb:hover { background: #3a6a8c; } .method-card { background: linear-gradient(145deg, #0a1e2b, #1a3a4f); border-radius: 8px; padding: 20px; border: 1px solid #2a5a7c; transition: all 0.3s ease; cursor: pointer; position: relative; overflow: hidden; } .method-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, #4dabf7, #ff922b); transform: scaleX(0); transition: transform 0.3s ease; } .method-card:hover { transform: translateY(-5px); border-color: #4dabf7; box-shadow: 0 8px 24px rgba(77, 171, 247, 0.15); } .method-card:hover::before { transform: scaleX(1); } .method-card.active { border-color: #51cf66; background: linear-gradient(145deg, #0a2b1a, #1a4f2b); } .method-name { color: #4dabf7; font-weight: bold; font-family: 'Fira Code', 'Courier New', monospace; margin-bottom: 10px; font-size: 1.1rem; } .method-desc { color: #a5d8ff; font-size: 0.95rem; line-height: 1.5; } .method-example { color: #8fa8bf; font-family: 'Fira Code', 'Courier New', monospace; font-size: 0.85rem; margin-top: 8px; padding: 8px; background-color: rgba(10, 30, 43, 0.5); border-radius: 4px; border-left: 3px solid #4dabf7; } .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 25px; padding-top: 25px; border-top: 1px solid #2a5a7c; } .stat-card { background-color: #0a1e2b; border-radius: 8px; padding: 18px; border: 1px solid #2a5a7c; text-align: center; } .stat-value { font-size: 2rem; font-weight: bold; color: #4dabf7; margin-bottom: 5px; } .stat-label { color: #a5d8ff; font-size: 0.9rem; } .progress-container { margin-top: 10px; } .progress-bar { height: 10px; background-color: #0a1e2b; border-radius: 5px; overflow: hidden; margin-bottom: 8px; } .progress { height: 100%; background: linear-gradient(90deg, #339af0, #4dabf7); width: 0%; transition: width 0.5s ease; border-radius: 5px; } .current-task { background: linear-gradient(145deg, #0a1e2b, #1a3a4f); border-radius: 10px; padding: 20px; border-left: 5px solid #4dabf7; margin-bottom: 25px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); } .task-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; } .task-id { background-color: #339af0; color: white; padding: 5px 12px; border-radius: 20px; font-size: 0.9rem; font-weight: bold; } .task-desc { font-size: 1.1rem; margin-bottom: 15px; line-height: 1.5; } .success { color: #51cf66; } .error { color: #ff6b6b; } .warning { color: #ff922b; } .completed { color: #8fa8bf; text-decoration: line-through; } .feedback { padding: 12px 18px; border-radius: 8px; margin-top: 15px; animation: fadeIn 0.3s ease; display: flex; align-items: center; gap: 10px; } .feedback.success { background-color: rgba(81, 207, 102, 0.1); border: 1px solid rgba(81, 207, 102, 0.3); } .feedback.error { background-color: rgba(255, 107, 107, 0.1); border: 1px solid rgba(255, 107, 107, 0.3); } .level-badge { display: inline-block; padding: 5px 12px; background: linear-gradient(90deg, #da77f2, #cc5de8); color: white; border-radius: 20px; font-size: 0.9rem; font-weight: bold; margin-left: 10px; } .category-selector { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; } .category-btn { padding: 8px 16px; background-color: #2a5a7c; border-radius: 6px; font-size: 0.9rem; transition: all 0.2s; } .category-btn.active { background-color: #339af0; } .achievement { background-color: rgba(255, 215, 0, 0.1); border: 1px solid rgba(255, 215, 0, 0.3); border-radius: 8px; padding: 15px; margin-top: 15px; animation: slideIn 0.5s ease; } .achievement-title { color: #ffd700; font-weight: bold; display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .visualization { margin-top: 20px; padding: 20px; background-color: #0a1e2b; border-radius: 8px; border: 1px solid #2a5a7c; } .data-structure { font-family: 'Fira Code', monospace; line-height: 1.8; padding: 15px; background-color: #0f2b3d; border-radius: 6px; overflow-x: auto; margin-top: 15px; } .variable-display { display: flex; flex-wrap: wrap; gap: 15px; margin-top: 15px; } .variable-card { background-color: #1a3a4f; border-radius: 6px; padding: 10px 15px; border: 1px solid #2a5a7c; min-width: 120px; } .variable-name { color: #4dabf7; font-weight: bold; font-family: 'Fira Code', monospace; margin-bottom: 5px; } .variable-value { color: #51cf66; font-family: 'Fira Code', monospace; word-break: break-all; } .run-button { margin-top: 10px; align-self: flex-start; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { transform: translateY(-10px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse { animation: pulse 0.5s ease; } @media (max-width: 1200px) { .main-content { flex-direction: column; } .simulator, .js-reference { min-width: 100%; } } @media (max-width: 768px) { .method-list { grid-template-columns: 1fr; } .button-group { flex-wrap: wrap; } button { flex: 1; min-width: 120px; } h1 { font-size: 2rem; } .stats { grid-template-columns: 1fr; } .category-selector { justify-content: center; } .editor-panel { flex-direction: column; } .js-editor, .console-output { min-width: 100%; } } .tab-navigation { display: flex; gap: 5px; margin-bottom: 20px; border-bottom: 1px solid #2a5a7c; padding-bottom: 15px; } .tab-btn { padding: 10px 20px; background-color: transparent; border: none; border-radius: 6px; color: #a5d8ff; cursor: pointer; transition: all 0.2s; font-weight: 500; } .tab-btn:hover { background-color: #2a5a7c; color: #e8f4fc; } .tab-btn.active { background-color: #2a5a7c; color: #f0f6fc; } .tab-content { display: none; } .tab-content.active { display: block; animation: fadeIn 0.3s ease; } .demo-area { display: flex; flex-direction: column; gap: 15px; margin-top: 20px; } .demo-controls { display: flex; gap: 10px; flex-wrap: wrap; } .demo-input { flex: 1; min-width: 200px; padding: 10px; background-color: #0a1e2b; border: 1px solid #2a5a7c; border-radius: 4px; color: #e8f4fc; font-family: 'Fira Code', monospace; } .demo-button { padding: 10px 20px; background-color: #339af0; color: white; border: none; border-radius: 4px; cursor: pointer; } .demo-output { background-color: #0a1e2b; border-radius: 6px; padding: 15px; min-height: 100px; border: 1px solid #2a5a7c; font-family: 'Fira Code', monospace; } .task-objective { background-color: rgba(77, 171, 247, 0.1); border-radius: 6px; padding: 15px; margin: 15px 0; border-left: 4px solid #4dabf7; } .objective-title { color: #4dabf7; font-weight: bold; margin-bottom: 8px; display: flex; align-items: center; gap: 8px; } .visual-preview { border: 2px solid #2a5a7c; border-radius: 8px; padding: 15px; margin: 15px 0; background-color: #0a1e2b; } .test-cases { margin-top: 20px; background-color: rgba(10, 30, 43, 0.5); border-radius: 6px; padding: 15px; border: 1px solid #2a5a7c; } .test-case { padding: 8px; margin-bottom: 10px; border-radius: 4px; background-color: #0f2b3d; } .test-case.passed { border-left: 4px solid #51cf66; } .test-case.failed { border-left: 4px solid #ff6b6b; } .test-input { color: #ff922b; font-family: 'Fira Code', monospace; } .test-expected { color: #51cf66; font-family: 'Fira Code', monospace; } .test-actual { color: #ff6b6b; font-family: 'Fira Code', monospace; } .code-line { counter-increment: line; padding-left: 2em; position: relative; } .code-line::before { content: counter(line); position: absolute; left: 0; color: #5c7c8a; width: 2em; text-align: right; padding-right: 0.5em; } .interactive-demo { display: flex; flex-direction: column; gap: 15px; margin-top: 15px; } .demo-element { padding: 15px; background-color: #1a3a4f; border-radius: 6px; border: 1px solid #2a5a7c; transition: all 0.3s ease; } .demo-element.clickable { cursor: pointer; } .demo-element.clickable:hover { background-color: #2a5a7c; transform: translateY(-2px); } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> </head> <body> <div class="container"> <header> <h1><i class="fab fa-js-square"></i> JavaScript Тренажер</h1> <p class="subtitle">Интерактивный симулятор для изучения JavaScript через практику. Пишите код, решайте задачи и отслеживайте прогресс.</p> </header> <div class="main-content"> <div class="simulator"> <div class="simulator-content"> <div class="task-header"> <h2>Рабочая область JavaScript</h2> <div class="task-id">Задача <span id="task-number">1</span>/<span id="total-tasks">15</span></div> </div> <div class="current-task"> <div class="task-desc" id="task-description">Объявите переменную и присвойте ей значение</div> <div class="task-objective"> <div class="objective-title"> <i class="fas fa-bullseye"></i> Цель задачи </div> <div id="task-objective">Создайте переменную с именем "message" и присвойте ей строковое значение "Привет, мир!"</div> </div> <div class="progress-container"> <div class="progress-bar"> <div class="progress" id="progress-bar"></div> </div> <div style="display: flex; justify-content: space-between; font-size: 0.9rem; color: #a5d8ff;"> <span>Прогресс</span> <span id="progress-text">0%</span> </div> </div> </div> <div class="workspace"> <div class="code-container"> <div class="editor-panel"> <div class="js-editor"> <div class="editor-title"> <i class="fas fa-code"></i> Редактор кода </div> <div class="code-area" id="js-code"> // Введите решение задачи ниже </div> <button class="run-button" id="run-btn"><i class="fas fa-play"></i> Запустить код</button> </div> <div class="console-output"> <div class="editor-title"> <i class="fas fa-terminal"></i> Консоль вывода </div> <div class="console-area" id="console-output"> // Результат выполнения кода появится здесь </div> </div> </div> <div class="preview-container"> <div class="preview-title"> <i class="fas fa-eye"></i> Предпросмотр </div> <div id="preview-area"> <!-- Здесь будет отображаться предпросмотр --> </div> </div> </div> </div> <div class="input-area"> <textarea id="js-input" placeholder="Введите JavaScript код (например: let x = 10;)" autofocus></textarea> </div> <div id="feedback" class="feedback" style="display: none;"></div> <div class="button-group"> <button class="hint-btn" id="hint-btn"><i class="fas fa-lightbulb"></i> Подсказка</button> <button class="skip-btn" id="skip-btn"><i class="fas fa-forward"></i> Пропустить</button> <button class="hint-btn" id="reset-btn"><i class="fas fa-redo"></i> Сбросить</button> <button id="submit-btn"><i class="fas fa-check"></i> Проверить решение</button> </div> <div class="test-cases" id="test-cases" style="display: none;"> <div class="objective-title"> <i class="fas fa-vial"></i> Тестовые случаи </div> <div id="test-cases-list"> <!-- Тестовые случаи будут добавляться динамически --> </div> </div> </div> <div class="tab-navigation"> <button class="tab-btn active" data-tab="stats"><i class="fas fa-chart-line"></i> Статистика</button> <button class="tab-btn" data-tab="achievements"><i class="fas fa-trophy"></i> Достижения</button> <button class="tab-btn" data-tab="visualization"><i class="fas fa-project-diagram"></i> Визуализация</button> </div> <div class="tab-content active" id="stats-tab"> <div class="stats"> <div class="stat-card"> <div class="stat-value" id="tasks-completed">0</div> <div class="stat-label">Задач выполнено</div> </div> <div class="stat-card"> <div class="stat-value" id="accuracy">100%</div> <div class="stat-label">Точность</div> </div> <div class="stat-card"> <div class="stat-value"><span id="level">1</span> <span class="level-badge" id="level-name">Новичок</span></div> <div class="stat-label">Уровень</div> </div> <div class="stat-card"> <div class="stat-value" id="xp">0</div> <div class="stat-label">Опыт (XP)</div> <div class="progress-bar" style="height: 6px; margin-top: 10px;"> <div class="progress" id="xp-progress" style="height: 100%;"></div> </div> </div> </div> </div> <div class="tab-content" id="achievements-tab"> <div id="achievements-list"> <!-- Достижения будут добавлены через JavaScript --> </div> </div> <div class="tab-content" id="visualization-tab"> <div class="visualization"> <h3>Визуализация данных и выполнения</h3> <p>Текущие значения переменных и состояние выполнения:</p> <div class="variable-display" id="variable-display"> <!-- Переменные будут отображаться здесь --> </div> <div class="visual-preview"> <div class="objective-title"> <i class="fas fa-flask"></i> Интерактивная демонстрация </div> <div class="interactive-demo"> <div class="demo-element clickable" id="demo-click"> Кликни на меня! (обработчик события) </div> <div class="demo-element" id="demo-text"> Текст для изменения </div> <div class="demo-controls"> <input type="text" class="demo-input" id="demo-input" placeholder="Введите текст..."> <button class="demo-button" id="demo-button">Применить</button> </div> </div> </div> </div> </div> </div> <div class="js-reference"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;"> <h2>Справочник JavaScript</h2> <div class="category-selector"> <button class="category-btn active" data-category="all">Все</button> <button class="category-btn" data-category="basics">Основы</button> <button class="category-btn" data-category="functions">Функции</button> <button class="category-btn" data-category="arrays">Массивы</button> <button class="category-btn" data-category="objects">Объекты</button> </div> </div> <div class="method-list" id="method-list"> <!-- JavaScript методы будут добавлены через JavaScript --> </div> </div> </div> </div> <script> // Данные для JavaScript тренажера const jsMethods = [ { method: "let/const/var", description: "Объявление переменных (блочная/глобальная область видимости)", example: "let x = 10; const y = 20; var z = 30;", category: "basics", difficulty: "easy" }, { method: "console.log()", description: "Вывод информации в консоль", example: "console.log('Привет, мир!');", category: "basics", difficulty: "easy" }, { method: "if/else", description: "Условный оператор", example: "if (x > 10) { console.log('Больше'); } else { console.log('Меньше'); }", category: "basics", difficulty: "easy" }, { method: "for цикл", description: "Цикл с известным количеством итераций", example: "for (let i = 0; i < 10; i++) { console.log(i); }", category: "basics", difficulty: "medium" }, { method: "while цикл", description: "Цикл с условием продолжения", example: "while (x < 10) { x++; }", category: "basics", difficulty: "medium" }, { method: "function", description: "Объявление функции", example: "function sum(a, b) { return a + b; }", category: "functions", difficulty: "easy" }, { method: "arrow function", description: "Стрелочная функция (современный синтаксис)", example: "const sum = (a, b) => a + b;", category: "functions", difficulty: "medium" }, { method: "Array.length", description: "Свойство, возвращающее длину массива", example: "let length = arr.length;", category: "arrays", difficulty: "easy" }, { method: "Array.push()", description: "Добавление элемента в конец массива", example: "arr.push('новый элемент');", category: "arrays", difficulty: "easy" }, { method: "Array.pop()", description: "Удаление последнего элемента массива", example: "let last = arr.pop();", category: "arrays", difficulty: "easy" }, { method: "Array.map()", description: "Создание нового массива на основе существующего", example: "let doubled = arr.map(x => x * 2);", category: "arrays", difficulty: "medium" }, { method: "Array.filter()", description: "Создание нового массива с элементами, прошедшими проверку", example: "let filtered = arr.filter(x => x > 10);", category: "arrays", difficulty: "medium" }, { method: "Array.reduce()", description: "Сведение массива к одному значению", example: "let sum = arr.reduce((acc, x) => acc + x, 0);", category: "arrays", difficulty: "hard" }, { method: "Object.keys()", description: "Получение массива ключей объекта", example: "let keys = Object.keys(obj);", category: "objects", difficulty: "medium" }, { method: "Object.values()", description: "Получение массива значений объекта", example: "let values = Object.values(obj);", category: "objects", difficulty: "medium" }, { method: "String.length", description: "Свойство, возвращающее длину строки", example: "let len = str.length;", category: "basics", difficulty: "easy" }, { method: "String.toUpperCase()", description: "Преобразование строки в верхний регистр", example: "let upper = str.toUpperCase();", category: "basics", difficulty: "easy" }, { method: "String.toLowerCase()", description: "Преобразование строки в нижний регистр", example: "let lower = str.toLowerCase();", category: "basics", difficulty: "easy" }, { method: "parseInt()", description: "Преобразование строки в целое число", example: "let num = parseInt('42');", category: "basics", difficulty: "easy" }, { method: "parseFloat()", description: "Преобразование строки в число с плавающей точкой", example: "let num = parseFloat('3.14');", category: "basics", difficulty: "easy" }, { method: "Math.random()", description: "Генерация случайного числа от 0 до 1", example: "let random = Math.random();", category: "basics", difficulty: "easy" }, { method: "Math.floor()", description: "Округление числа вниз до ближайшего целого", example: "let floor = Math.floor(3.7); // 3", category: "basics", difficulty: "easy" }, { method: "Date.now()", description: "Текущее время в миллисекундах", example: "let timestamp = Date.now();", category: "basics", difficulty: "medium" }, { method: "JSON.parse()", description: "Преобразование JSON строки в объект JavaScript", example: "let obj = JSON.parse('{\"key\": \"value\"}');", category: "objects", difficulty: "medium" }, { method: "JSON.stringify()", description: "Преобразование объекта JavaScript в JSON строку", example: "let json = JSON.stringify({key: 'value'});", category: "objects", difficulty: "medium" }, { method: "addEventListener()", description: "Добавление обработчика событий к элементу", example: "element.addEventListener('click', handler);", category: "basics", difficulty: "medium" }, { method: "setTimeout()", description: "Выполнение функции с задержкой", example: "setTimeout(() => console.log('Прошло 1 сек'), 1000);", category: "basics", difficulty: "medium" }, { method: "setInterval()", description: "Повторное выполнение функции с интервалом", example: "setInterval(() => console.log('Прошла секунда'), 1000);", category: "basics", difficulty: "medium" }, { method: "try/catch", description: "Обработка ошибок в JavaScript", example: "try { riskyCode(); } catch(error) { console.log(error); }", category: "basics", difficulty: "medium" }, { method: "Promise", description: "Объект для работы с асинхронными операциями", example: "new Promise((resolve, reject) => { /* код */ })", category: "basics", difficulty: "hard" } ]; // Задачи для JavaScript тренажера const tasks = [ { id: 1, description: "Объявите переменную и присвойте ей значение", objective: "Создайте переменную с именем 'message' и присвойте ей строковое значение 'Привет, мир!'", template: "// Объявите переменную message и присвойте ей значение 'Привет, мир!'\n", solution: "let message = 'Привет, мир!';", test: `() => { try { eval(\`\${userCode}\\n\`); return typeof message !== 'undefined' && message === 'Привет, мир!'; } catch(e) { return false; } }`, hint: "Используйте let или const для объявления переменной, затем оператор присваивания =", category: "basics", xp: 10, checkType: "eval" }, { id: 2, description: "Выведите значение переменной в консоль", objective: "Создайте переменную number со значением 42 и выведите её в консоль", template: "// Создайте переменную number со значением 42 и выведите её\n", solution: "let number = 42;\nconsole.log(number);", test: `() => { let logs = []; const originalLog = console.log; console.log = (...args) => logs.push(args.join(' ')); try { eval(\`\${userCode}\\n\`); console.log = originalLog; return logs.some(log => log.includes('42')); } catch(e) { console.log = originalLog; return false; } }`, hint: "Используйте console.log() для вывода значения", category: "basics", xp: 15, checkType: "eval" }, { id: 3, description: "Напишите функцию сложения двух чисел", objective: "Создайте функцию add, которая принимает два параметра и возвращает их сумму", template: "// Напишите функцию add, которая возвращает сумму двух чисел\n", solution: "function add(a, b) {\n return a + b;\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return typeof add === 'function' && add(2, 3) === 5 && add(10, -5) === 5; } catch(e) { return false; } }`, hint: "Используйте ключевое слово function, параметры в скобках и return для возврата результата", category: "functions", xp: 20, checkType: "eval" }, { id: 4, description: "Используйте условный оператор if/else", objective: "Напишите функцию checkAge, которая возвращает 'Взрослый' если возраст >= 18, иначе 'Ребенок'", template: "// Напишите функцию checkAge, которая проверяет возраст\n", solution: "function checkAge(age) {\n if (age >= 18) {\n return 'Взрослый';\n } else {\n return 'Ребенок';\n }\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return checkAge(20) === 'Взрослый' && checkAge(15) === 'Ребенок'; } catch(e) { return false; } }`, hint: "Используйте if (age >= 18) для проверки условия", category: "basics", xp: 20, checkType: "eval" }, { id: 5, description: "Создайте и используйте массив", objective: "Создайте массив чисел от 1 до 5 и добавьте число 6 в конец массива", template: "// Создайте массив чисел и добавьте элемент\n", solution: "let numbers = [1, 2, 3, 4, 5];\nnumbers.push(6);", test: `() => { try { eval(\`\${userCode}\\n\`); return Array.isArray(numbers) && numbers.length === 6 && numbers[5] === 6; } catch(e) { return false; } }`, hint: "Используйте квадратные скобки [] для создания массива и метод push() для добавления элемента", category: "arrays", xp: 20, checkType: "eval" }, { id: 6, description: "Используйте цикл for для перебора массива", objective: "Напишите функцию sumArray, которая суммирует все элементы массива чисел", template: "// Напишите функцию sumArray, которая суммирует элементы массива\n", solution: "function sumArray(arr) {\n let sum = 0;\n for (let i = 0; i < arr.length; i++) {\n sum += arr[i];\n }\n return sum;\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return sumArray([1, 2, 3]) === 6 && sumArray([10, 20, 30]) === 60; } catch(e) { return false; } }`, hint: "Используйте for (let i = 0; i < arr.length; i++) для перебора массива", category: "basics", xp: 25, checkType: "eval" }, { id: 7, description: "Работа с объектами", objective: "Создайте объект user с полями name, age и isAdmin, затем получите значение name", template: "// Создайте объект user и получите значение поля name\n", solution: "let user = {\n name: 'Иван',\n age: 25,\n isAdmin: true\n};\nlet userName = user.name;", test: `() => { try { eval(\`\${userCode}\\n\`); return user && user.name === 'Иван' && userName === 'Иван'; } catch(e) { return false; } }`, hint: "Используйте фигурные скобки {} для создания объекта и точку . для доступа к свойствам", category: "objects", xp: 20, checkType: "eval" }, { id: 8, description: "Используйте метод map для преобразования массива", objective: "Создайте массив чисел и используйте map для создания нового массива с удвоенными значениями", template: "// Используйте map для удвоения каждого элемента массива\n", solution: "let numbers = [1, 2, 3, 4, 5];\nlet doubled = numbers.map(function(num) {\n return num * 2;\n});", test: `() => { try { eval(\`\${userCode}\\n\`); return doubled && doubled.length === 5 && doubled[0] === 2 && doubled[4] === 10; } catch(e) { return false; } }`, hint: "Используйте метод map() с функцией, которая возвращает num * 2", category: "arrays", xp: 25, checkType: "eval" }, { id: 9, description: "Фильтрация массива", objective: "Используйте filter для создания нового массива только с четными числами", template: "// Используйте filter для отбора четных чисел\n", solution: "let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\nlet evenNumbers = numbers.filter(function(num) {\n return num % 2 === 0;\n});", test: `() => { try { eval(\`\${userCode}\\n\`); return evenNumbers.length === 5 && evenNumbers[0] === 2 && evenNumbers[4] === 10; } catch(e) { return false; } }`, hint: "Используйте метод filter() с функцией, которая проверяет num % 2 === 0", category: "arrays", xp: 25, checkType: "eval" }, { id: 10, description: "Работа со строками", objective: "Напишите функцию, которая принимает строку и возвращает её в верхнем регистре", template: "// Напишите функцию toUpperCaseString\n", solution: "function toUpperCaseString(str) {\n return str.toUpperCase();\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return toUpperCaseString('hello') === 'HELLO' && toUpperCaseString('JavaScript') === 'JAVASCRIPT'; } catch(e) { return false; } }`, hint: "Используйте метод toUpperCase() для строки", category: "basics", xp: 15, checkType: "eval" }, { id: 11, description: "Обработка событий", objective: "Добавьте обработчик клика на кнопку, который выводит сообщение в консоль", template: "// Добавьте обработчик события клика на элемент с id='myButton'\n// и выведите 'Кнопка нажата!' в консоль\n", solution: "document.getElementById('myButton').addEventListener('click', function() {\n console.log('Кнопка нажата!');\n});", test: `() => { // Для этой задачи проверяем только синтаксис const code = userCode; return code.includes('addEventListener') && code.includes('click') && code.includes('console.log'); }`, hint: "Используйте addEventListener('click', функция) для добавления обработчика", category: "basics", xp: 30, checkType: "syntax" }, { id: 12, description: "Асинхронный код с setTimeout", objective: "Используйте setTimeout для вывода сообщения с задержкой 1 секунда", template: "// Используйте setTimeout для вывода сообщения с задержкой\n", solution: "setTimeout(function() {\n console.log('Сообщение после задержки');\n}, 1000);", test: `() => { // Проверяем наличие setTimeout с правильными параметрами const code = userCode; return code.includes('setTimeout') && code.includes('1000') && code.includes('console.log'); }`, hint: "Используйте setTimeout(функция, 1000) где 1000 - это задержка в миллисекундах", category: "basics", xp: 25, checkType: "syntax" }, { id: 13, description: "Работа с JSON", objective: "Преобразуйте строку JSON в объект и получите значение поля", template: "// Преобразуйте JSON строку в объект и получите значение name\n", solution: `let jsonString = '{"name": "Анна", "age": 30}';\nlet obj = JSON.parse(jsonString);\nlet name = obj.name;`, test: `() => { try { eval(\`\${userCode}\\n\`); return name === 'Анна' && obj.age === 30; } catch(e) { return false; } }`, hint: "Используйте JSON.parse() для преобразования строки в объект", category: "objects", xp: 25, checkType: "eval" }, { id: 14, description: "Поиск максимального значения в массиве", objective: "Напишите функцию findMax, которая находит максимальное значение в массиве чисел", template: "// Напишите функцию findMax\n", solution: "function findMax(arr) {\n let max = arr[0];\n for (let i = 1; i < arr.length; i++) {\n if (arr[i] > max) {\n max = arr[i];\n }\n }\n return max;\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return findMax([1, 5, 3, 9, 2]) === 9 && findMax([-10, -5, -1]) === -1; } catch(e) { return false; } }`, hint: "Используйте цикл for для перебора массива и сравнения значений", category: "basics", xp: 30, checkType: "eval" }, { id: 15, description: "Рекурсивная функция", objective: "Напишите рекурсивную функцию для вычисления факториала числа", template: "// Напишите рекурсивную функцию factorial\n", solution: "function factorial(n) {\n if (n <= 1) {\n return 1;\n }\n return n * factorial(n - 1);\n}", test: `() => { try { eval(\`\${userCode}\\n\`); return factorial(5) === 120 && factorial(1) === 1 && factorial(0) === 1; } catch(e) { return false; } }`, hint: "Используйте условие if (n <= 1) return 1; для базового случая", category: "functions", xp: 35, checkType: "eval" } ]; // Достижения const achievements = [ { id: 1, name: "Первая программа", description: "Выполните первую задачу", icon: "fas fa-code", unlocked: false, xpReward: 20 }, { id: 2, name: "Начинающий программист", description: "Выполните 5 задач", icon: "fas fa-laptop-code", unlocked: false, xpReward: 50 }, { id: 3, name: "Мастер функций", description: "Создайте 3 разные функции", icon: "fas fa-cogs", unlocked: false, xpReward: 60 }, { id: 4, name: "Массивный успех", description: "Используйте все основные методы массивов (push, pop, map, filter)", icon: "fas fa-layer-group", unlocked: false, xpReward: 40 }, { id: 5, name: "JavaScript Мастер", description: "Достигните 5 уровня", icon: "fas fa-crown", unlocked: false, xpReward: 100 } ]; // Элементы DOM const jsCode = document.getElementById('js-code'); const consoleOutput = document.getElementById('console-output'); const jsInput = document.getElementById('js-input'); const submitBtn = document.getElementById('submit-btn'); const hintBtn = document.getElementById('hint-btn'); const skipBtn = document.getElementById('skip-btn'); const resetBtn = document.getElementById('reset-btn'); const runBtn = document.getElementById('run-btn'); const taskDescription = document.getElementById('task-description'); const taskObjective = document.getElementById('task-objective'); const progressBar = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); const tasksCompleted = document.getElementById('tasks-completed'); const totalTasks = document.getElementById('total-tasks'); const taskNumber = document.getElementById('task-number'); const accuracyElement = document.getElementById('accuracy'); const levelElement = document.getElementById('level'); const levelNameElement = document.getElementById('level-name'); const xpElement = document.getElementById('xp'); const xpProgress = document.getElementById('xp-progress'); const methodList = document.getElementById('method-list'); const feedback = document.getElementById('feedback'); const achievementsList = document.getElementById('achievements-list'); const previewArea = document.getElementById('preview-area'); const variableDisplay = document.getElementById('variable-display'); const testCases = document.getElementById('test-cases'); const testCasesList = document.getElementById('test-cases-list'); const tabButtons = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); const categoryButtons = document.querySelectorAll('.category-btn'); const demoClick = document.getElementById('demo-click'); const demoText = document.getElementById('demo-text'); const demoInput = document.getElementById('demo-input'); const demoButton = document.getElementById('demo-button'); // Состояние тренажера let currentTask = 0; let completedTasks = 0; let correctAnswers = 0; let totalAnswers = 0; let xp = 0; let level = 1; let userStats = { methodsUsed: {}, tasksCompleted: [], achievementsUnlocked: [] }; // Перехват console.log для вывода в интерфейс let consoleLogs = []; const originalConsoleLog = console.log; const originalConsoleError = console.error; const originalConsoleWarn = console.warn; const originalConsoleInfo = console.info; // Инициализация из localStorage function loadProgress() { const savedProgress = localStorage.getItem('jsTrainerProgress'); if (savedProgress) { const progress = JSON.parse(savedProgress); currentTask = progress.currentTask || 0; completedTasks = progress.completedTasks || 0; correctAnswers = progress.correctAnswers || 0; totalAnswers = progress.totalAnswers || 0; xp = progress.xp || 0; level = progress.level || 1; userStats = progress.userStats || { methodsUsed: {}, tasksCompleted: [], achievementsUnlocked: [] }; // Обновляем уровень на основе XP updateLevelFromXP(); } } // Сохранение прогресса function saveProgress() { const progress = { currentTask, completedTasks, correctAnswers, totalAnswers, xp, level, userStats, timestamp: new Date().toISOString() }; localStorage.setItem('jsTrainerProgress', JSON.stringify(progress)); } // Инициализация методов function initMethods() { renderMethods('all'); totalTasks.textContent = tasks.length; updateTask(); initAchievements(); initDemoElements(); } // Рендеринг методов с фильтрацией function renderMethods(category) { methodList.innerHTML = ''; let filteredMethods = jsMethods; if (category !== 'all') { filteredMethods = jsMethods.filter(method => method.category === category); } filteredMethods.forEach(method => { const methodCard = document.createElement('div'); methodCard.className = 'method-card'; if (userStats.methodsUsed[method.method] > 2) { methodCard.classList.add('active'); } methodCard.innerHTML = ` <div class="method-name">${method.method}</div> <div class="method-desc">${method.description}</div> <div class="method-example">${method.example}</div> ${userStats.methodsUsed[method.method] ? `<div style="margin-top: 10px; font-size: 0.8rem; color: #51cf66;"> <i class="fas fa-check-circle"></i> Использовано: ${userStats.methodsUsed[method.method]} раз </div>` : '' } `; // Добавляем обработчик клика для заполнения метода methodCard.addEventListener('click', () => { jsInput.value = method.example; jsInput.focus(); }); methodList.appendChild(methodCard); }); } // Инициализация достижений function initAchievements() { achievementsList.innerHTML = ''; achievements.forEach(achievement => { const achievementEl = document.createElement('div'); achievementEl.className = `achievement ${achievement.unlocked ? 'unlocked' : 'locked'}`; achievementEl.innerHTML = ` <div class="achievement-title"> <i class="${achievement.icon}"></i> ${achievement.name} ${achievement.unlocked ? '<i class="fas fa-unlock" style="color: #51cf66;"></i>' : '<i class="fas fa-lock" style="color: #a5d8ff;"></i>'} </div> <div style="color: #a5d8ff; font-size: 0.9rem;">${achievement.description}</div> ${achievement.unlocked ? `<div style="margin-top: 8px; color: #ffd700; font-size: 0.8rem;"> <i class="fas fa-star"></i> Награда: ${achievement.xpReward} XP </div>` : '' } `; achievementsList.appendChild(achievementEl); }); } // Проверка достижений function checkAchievements() { let newAchievements = []; // Первая программа if (completedTasks >= 1 && !achievements[0].unlocked) { achievements[0].unlocked = true; newAchievements.push(achievements[0]); } // Начинающий программист if (completedTasks >= 5 && !achievements[1].unlocked) { achievements[1].unlocked = true; newAchievements.push(achievements[1]); } // Мастер функций const functionTasks = userStats.tasksCompleted.filter(id => { const task = tasks.find(t => t.id === id); return task && task.category === 'functions'; }).length; if (functionTasks >= 3 && !achievements[2].unlocked) { achievements[2].unlocked = true; newAchievements.push(achievements[2]); } // Массивный успех const arrayMethods = ['Array.push()', 'Array.pop()', 'Array.map()', 'Array.filter()']; const usedArrayMethods = arrayMethods.filter(method => userStats.methodsUsed[method] > 0); if (usedArrayMethods.length >= arrayMethods.length && !achievements[3].unlocked) { achievements[3].unlocked = true; newAchievements.push(achievements[3]); } // JavaScript Мастер if (level >= 5 && !achievements[4].unlocked) { achievements[4].unlocked = true; newAchievements.push(achievements[4]); } // Награждаем XP за новые достижения newAchievements.forEach(achievement => { if (!userStats.achievementsUnlocked.includes(achievement.id)) { xp += achievement.xpReward; userStats.achievementsUnlocked.push(achievement.id); // Показываем уведомление showFeedback(`🎉 Достижение разблокировано: ${achievement.name}! +${achievement.xpReward} XP`, 'success'); // Обновляем визуализацию достижений initAchievements(); updateLevelFromXP(); } }); saveProgress(); } // Обновление текущей задачи function updateTask() { if (currentTask < tasks.length) { const task = tasks[currentTask]; taskDescription.textContent = task.description; taskObjective.textContent = task.objective; taskNumber.textContent = currentTask + 1; const progressPercent = Math.round((completedTasks / tasks.length) * 100); progressBar.style.width = `${progressPercent}%`; progressText.textContent = `${progressPercent}%`; tasksCompleted.textContent = completedTasks; // Обновляем редактор кода updateCodeEditor(task); // Очищаем консоль clearConsole(); // Обновляем предпросмотр updatePreview(task); // Скрываем тестовые случаи testCases.style.display = 'none'; // Подсветка связанных методов highlightRelevantMethods(task); } else { taskDescription.textContent = "🎉 Поздравляем! Вы выполнили все задачи!"; taskDescription.classList.add('success'); taskObjective.textContent = "Теперь вы знаете основы JavaScript!"; jsInput.disabled = true; submitBtn.disabled = true; runBtn.disabled = true; progressBar.style.width = '100%'; progressText.textContent = '100%'; showFeedback("Вы завершили все задачи тренажера! Теперь вы знаете основы JavaScript!", 'success'); } updateStats(); saveProgress(); } // Обновление редактора кода function updateCodeEditor(task) { jsCode.textContent = task.template; jsCode.style.color = '#e8f4fc'; jsInput.value = ''; // Добавляем подсветку синтаксиса highlightSyntax(); } // Подсветка синтаксиса в редакторе function highlightSyntax() { const code = jsCode.textContent; let highlighted = code .replace(/(\/\/.*)/g, '<span class="js-comment">$1</span>') .replace(/\b(let|const|var|function|if|else|for|while|return)\b/g, '<span class="js-keyword">$1</span>') .replace(/\b(console|Math|JSON|Date)\b/g, '<span class="js-function">$1</span>') .replace(/(\d+)/g, '<span class="js-number">$1</span>') .replace(/("[^"]*"|'[^']*')/g, '<span class="js-string">$1</span>') .replace(/([=+\-*\/<>!&|]+)/g, '<span class="js-operator">$1</span>'); jsCode.innerHTML = highlighted; } // Очистка консоли function clearConsole() { consoleOutput.textContent = '// Результат выполнения кода появится здесь'; consoleLogs = []; } // Обновление предпросмотра function updatePreview(task) { // Очищаем предпросмотр previewArea.innerHTML = ''; // Для некоторых задач добавляем специальные элементы if (task.id === 11) { // Задача с обработчиком событий previewArea.innerHTML = ` <button id="myButton" style="padding: 10px 20px; background-color: #339af0; color: white; border: none; border-radius: 4px; cursor: pointer;"> Нажми меня </button> <p style="margin-top: 10px; color: #666;">Нажмите на кнопку, чтобы проверить обработчик события</p> `; } else { previewArea.innerHTML = `<p style="color: #666;">Выполните код, чтобы увидеть результат</p>`; } } // Подсветка релевантных методов function highlightRelevantMethods(task) { document.querySelectorAll('.method-card').forEach(card => { const methodName = card.querySelector('.method-name').textContent; // Проверяем, связан ли метод с задачей let isRelevant = false; const solution = task.solution; // Простая проверка по ключевым словам if (task.category === 'arrays' && methodName.includes('Array.')) { isRelevant = true; } else if (task.category === 'functions' && (methodName.includes('function') || methodName.includes('=>'))) { isRelevant = true; } else if (task.category === 'objects' && methodName.includes('Object.')) { isRelevant = true; } else if (solution.includes(methodName.split('(')[0])) { isRelevant = true; } if (isRelevant) { card.style.boxShadow = '0 0 0 2px rgba(77, 171, 247, 0.5)'; card.style.transform = 'translateY(-3px)'; } else { card.style.boxShadow = ''; card.style.transform = ''; } }); } // Запуск кода пользователя function runUserCode(code) { clearConsole(); // Перехватываем console.log для отображения в интерфейсе console.log = function(...args) { consoleLogs.push({type: 'log', args}); originalConsoleLog.apply(console, args); // Обновляем вывод в интерфейсе updateConsoleOutput(); }; console.error = function(...args) { consoleLogs.push({type: 'error', args}); originalConsoleError.apply(console, args); updateConsoleOutput(); }; console.warn = function(...args) { consoleLogs.push({type: 'warn', args}); originalConsoleWarn.apply(console, args); updateConsoleOutput(); }; console.info = function(...args) { consoleLogs.push({type: 'info', args}); originalConsoleInfo.apply(console, args); updateConsoleOutput(); }; try { // Безопасное выполнение кода const safeCode = ` (function() { "use strict"; ${code} })(); `; eval(safeCode); // Восстанавливаем оригинальные console методы console.log = originalConsoleLog; console.error = originalConsoleError; console.warn = originalConsoleWarn; console.info = originalConsoleInfo; // Обновляем визуализацию переменных updateVariableDisplay(); return { success: true }; } catch (error) { // Восстанавливаем оригинальные console методы console.log = originalConsoleLog; console.error = originalConsoleError; console.warn = originalConsoleWarn; console.info = originalConsoleInfo; // Добавляем ошибку в вывод консоли consoleLogs.push({type: 'error', args: [error.message]}); updateConsoleOutput(); return { success: false, error: error.message }; } } // Обновление вывода консоли function updateConsoleOutput() { let output = ''; consoleLogs.forEach(log => { const message = log.args.map(arg => { if (typeof arg === 'object') { try { return JSON.stringify(arg); } catch { return String(arg); } } return String(arg); }).join(' '); output += `<div class="console-line console-${log.type}">${message}</div>`; }); consoleOutput.innerHTML = output; // Прокручиваем консоль вниз consoleOutput.scrollTop = consoleOutput.scrollHeight; } // Обновление отображения переменных function updateVariableDisplay() { // Эта функция будет пытаться получить доступ к переменным, объявленным в глобальной области видимости // В реальном приложении это было бы сложнее из-за sandboxing variableDisplay.innerHTML = '<p>Переменные будут отображаться после выполнения кода</p>'; } // Проверка решения задачи function checkSolution(code) { const task = tasks[currentTask]; const userCode = code || jsInput.value.trim(); if (!userCode) { showFeedback("Введите код для проверки", 'warning'); return; } // Увеличиваем счетчик ответов totalAnswers++; // Регистрируем использование методов registerMethodsUsed(userCode); // Запускаем код пользователя const result = runUserCode(userCode); // Проверяем решение let isCorrect = false; let testResults = []; if (task.checkType === "eval") { // Проверка через выполнение кода try { const testFunction = eval(`(${task.test})`); isCorrect = testFunction(); if (!isCorrect) { showFeedback("❌ Решение неверное. Проверьте логику вашего кода.", 'error'); } } catch (error) { showFeedback(`❌ Ошибка при проверке: ${error.message}`, 'error'); isCorrect = false; } } else if (task.checkType === "syntax") { // Проверка синтаксиса const testFunction = eval(`(${task.test})`); isCorrect = testFunction(); if (!isCorrect) { showFeedback("❌ В коде отсутствуют необходимые элементы. Проверьте подсказку.", 'error'); } } if (isCorrect) { // Правильное решение correctAnswers++; // Добавляем анимацию submitBtn.classList.add('pulse'); setTimeout(() => { submitBtn.classList.remove('pulse'); }, 500); showFeedback(`✅ Правильно! +${task.xp} XP`, 'success'); // Обновляем редактор с правильным решением jsCode.textContent = task.solution; highlightSyntax(); // Обновляем статистику xp += task.xp; updateStats(); // Добавляем задачу в выполненные if (!userStats.tasksCompleted.includes(task.id)) { userStats.tasksCompleted.push(task.id); } // Проверяем достижения checkAchievements(); // Показываем тестовые случаи showTestCases(task, true); // Переходим к следующей задаче setTimeout(() => { currentTask++; completedTasks++; updateTask(); jsInput.value = ''; jsInput.focus(); }, 2000); } else { // Неправильное решение showFeedback("❌ Попробуйте еще раз. Используйте подсказку если нужно.", 'error'); // Показываем подсказку if (totalAnswers % 3 === 0) { showFeedback(`💡 Подсказка: ${task.hint}`, 'warning'); } updateStats(); // Показываем тестовые случаи showTestCases(task, false); } // Обновляем точность const accuracy = totalAnswers > 0 ? Math.round((correctAnswers / totalAnswers) * 100) : 100; accuracyElement.textContent = `${accuracy}%`; // Обновляем список методов renderMethods(document.querySelector('.category-btn.active').dataset.category); saveProgress(); } // Показать тестовые случаи function showTestCases(task, passed) { testCases.style.display = 'block'; testCasesList.innerHTML = ''; // Для простоты создаем несколько тестовых случаев const testCasesData = [ { input: "Базовый тест", expected: "Правильное решение", actual: passed ? "Пройдено" : "Не пройдено" }, { input: "Синтаксис", expected: "Корректный синтаксис", actual: passed ? "Корректный" : "Есть ошибки" }, { input: "Логика", expected: "Правильная логика", actual: passed ? "Правильная" : "Требуется доработка" } ]; testCasesData.forEach(testCase => { const testCaseEl = document.createElement('div'); testCaseEl.className = `test-case ${passed ? 'passed' : 'failed'}`; testCaseEl.innerHTML = ` <div><strong>Входные данные:</strong> <span class="test-input">${testCase.input}</span></div> <div><strong>Ожидаемый результат:</strong> <span class="test-expected">${testCase.expected}</span></div> <div><strong>Фактический результат:</strong> <span class="test-actual">${testCase.actual}</span></div> `; testCasesList.appendChild(testCaseEl); }); } // Регистрация использованных методов function registerMethodsUsed(code) { jsMethods.forEach(method => { const methodName = method.method.split('(')[0]; // Проверяем, содержится ли метод в коде if (code.includes(methodName)) { if (!userStats.methodsUsed[method.method]) { userStats.methodsUsed[method.method] = 0; } userStats.methodsUsed[method.method]++; } }); } // Инициализация демо-элементов function initDemoElements() { // Обработчик для демо-элемента demoClick.addEventListener('click', function() { demoText.textContent = 'Элемент был кликнут!'; demoText.style.color = '#51cf66'; // Логируем в консоль console.log('Демо-элемент был кликнут'); }); // Обработчик для демо-кнопки demoButton.addEventListener('click', function() { const text = demoInput.value.trim(); if (text) { demoText.textContent = text; demoText.style.color = '#4dabf7'; console.log(`Текст изменен на: ${text}`); } }); // Обработчик Enter в демо-поле ввода demoInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') { demoButton.click(); } }); } // Показать фидбек function showFeedback(message, type) { feedback.textContent = message; feedback.className = `feedback ${type}`; feedback.style.display = 'flex'; setTimeout(() => { feedback.style.display = 'none'; }, 3000); } // Обновление статистики function updateStats() { const accuracy = totalAnswers > 0 ? Math.round((correctAnswers / totalAnswers) * 100) : 100; accuracyElement.textContent = `${accuracy}%`; xpElement.textContent = xp; // Обновляем прогресс XP const xpForNextLevel = level * 100; const xpProgressPercent = Math.min(100, Math.round((xp % xpForNextLevel) / xpForNextLevel * 100)); xpProgress.style.width = `${xpProgressPercent}%`; // Обновляем уровень updateLevelFromXP(); } // Обновление уровня на основе XP function updateLevelFromXP() { const newLevel = Math.floor(xp / 100) + 1; if (newLevel !== level) { level = newLevel; levelElement.textContent = level; levelNameElement.textContent = getLevelName(level); // Показываем сообщение о новом уровне if (level > 1) { showFeedback(`🎉 Новый уровень! Теперь вы ${getLevelName(level)} (уровень ${level})`, 'success'); } } } // Получить название уровня function getLevelName(level) { if (level < 3) return "Новичок"; if (level < 5) return "Ученик"; if (level < 8) return "Опытный"; if (level < 12) return "Эксперт"; return "Мастер JavaScript"; } // Показать подсказку function showHint() { const task = tasks[currentTask]; showFeedback(`💡 Подсказка: ${task.hint}`, 'warning'); } // Пропустить задачу function skipTask() { if (currentTask < tasks.length - 1) { currentTask++; updateTask(); jsInput.value = ''; jsInput.focus(); showFeedback("Задача пропущена", 'warning'); saveProgress(); } } // Сбросить прогресс function resetProgress() { if (confirm("Вы уверены, что хотите сбросить весь прогресс?")) { currentTask = 0; completedTasks = 0; correctAnswers = 0; totalAnswers = 0; xp = 0; level = 1; userStats = { methodsUsed: {}, tasksCompleted: [], achievementsUnlocked: [] }; // Сброс достижений achievements.forEach(a => a.unlocked = false); // Сброс демо-элементов demoText.textContent = 'Текст для изменения'; demoText.style.color = ''; demoInput.value = ''; localStorage.removeItem('jsTrainerProgress'); updateTask(); updateStats(); initMethods(); showFeedback("Прогресс сброшен", 'success'); } } // Запустить код пользователя (без проверки) function runCode() { const code = jsInput.value.trim(); if (!code) { showFeedback("Введите код для запуска", 'warning'); return; } const result = runUserCode(code); if (result.success) { showFeedback("Код выполнен успешно", 'success'); } else { showFeedback(`Ошибка выполнения: ${result.error}`, 'error'); } } // Обработчики событий submitBtn.addEventListener('click', () => { checkSolution(); }); runBtn.addEventListener('click', runCode); jsInput.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'Enter') { e.preventDefault(); runCode(); } }); hintBtn.addEventListener('click', showHint); skipBtn.addEventListener('click', skipTask); resetBtn.addEventListener('click', resetProgress); // Табы tabButtons.forEach(button => { button.addEventListener('click', () => { const tabId = button.dataset.tab; // Убираем активный класс у всех кнопок и контента tabButtons.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); // Добавляем активный класс текущей кнопке и контенту button.classList.add('active'); document.getElementById(`${tabId}-tab`).classList.add('active'); }); }); // Фильтры категорий categoryButtons.forEach(button => { button.addEventListener('click', () => { categoryButtons.forEach(btn => btn.classList.remove('active')); button.classList.add('active'); renderMethods(button.dataset.category); }); }); // Инициализация loadProgress(); initMethods(); jsInput.focus(); // Добавляем горячие клавиши document.addEventListener('keydown', (e) => { // Ctrl+Enter для подсказки if (e.ctrlKey && e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); showHint(); } // Ctrl+Shift+Enter для пропуска if (e.ctrlKey && e.shiftKey && e.key === 'Enter') { e.preventDefault(); skipTask(); } }); </script> </body> </html>