From 9a68ecccfcdaa73c3ec1c67a2ca4cb31a7fe54a6 Mon Sep 17 00:00:00 2001 From: Serafim Date: Sat, 14 Feb 2026 15:31:14 +0300 Subject: [PATCH] init --- favicon.svg | 22 ++ index.html | 226 +++++++++++++++ questions.json | 332 ++++++++++++++++++++++ script.js | 499 +++++++++++++++++++++++++++++++++ style.css | 732 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1811 insertions(+) create mode 100644 favicon.svg create mode 100644 index.html create mode 100644 questions.json create mode 100644 script.js create mode 100644 style.css diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..b06a79e --- /dev/null +++ b/favicon.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..26b128e --- /dev/null +++ b/index.html @@ -0,0 +1,226 @@ + + + + + + Тестирование по мультимедиа + + + + + +
+
+

+ Тестирование по мультимедиа +

+

+ Проверьте свои знания в области мультимедийных технологий +

+
+ +
+
+
+

Инструкция к тесту

+
    +
  • + Всего 30 вопросов по теме + мультимедиа +
  • +
  • + Вопросы и варианты ответов + перемешиваются +
  • +
  • + На прохождение теста нет + ограничений по времени +
  • +
  • + Можно выбирать несколько + вариантов ответа +
  • +
  • + Проверка происходит при + нажатии "Следующий" +
  • +
  • + Полный балл за вопрос - если выбраны + все правильные ответы +
  • +
  • + Результат будет показан после + завершения теста +
  • +
+ +
+
+ + + + + + +
+ +
+

Гарантия верности ответов не даётся!

+
+
+ + + + diff --git a/questions.json b/questions.json new file mode 100644 index 0000000..9614f4c --- /dev/null +++ b/questions.json @@ -0,0 +1,332 @@ +[ + { + "text": "Составляющие мультимедиа могут быть разбиты на основные группы?", + "options": [ + { + "text": "Текстовая, визуальная и звуковая информация.", + "correct": true + }, + { + "text": "Текстовая, визуальная, звуковая информация и данные.", + "correct": false + }, + { + "text": "Текстовая, визуальная, звуковая информация, данные и графическая информация.", + "correct": false + } + ] + }, + { + "text": "С точки зрения передачи мультимедиа могут быть классифицированы на?", + "options": [ + { "text": "Передаваемые в реальном времени.", "correct": true }, + { "text": "Передаваемые в on-line.", "correct": false }, + { "text": "Передаваемые в не реальном времени.", "correct": true } + ] + }, + { + "text": "Самый популярный тип мультимедиа?", + "options": [ + { "text": "Видео.", "correct": false }, + { "text": "Звук.", "correct": false }, + { "text": "Текст.", "correct": true } + ] + }, + { + "text": "Прогрессивное сжатие достигается?", + "options": [ + { "text": "Прогрессивным кодированием.", "correct": true }, + { "text": "Векторной квантизацией.", "correct": true }, + { "text": "Пирамидальным кодированием.", "correct": true } + ] + }, + { + "text": "В Интернет-телефонии человек может спокойно относиться к задержкам не более?", + "options": [ + { "text": "200 мс.", "correct": true }, + { "text": "250 мс.", "correct": false }, + { "text": "300 мс.", "correct": false } + ] + }, + { + "text": "Управление сессиями включает?", + "options": [ + { "text": "Запрос на сессию, удаление сессии.", "correct": false }, + { + "text": "Описание типа мультимедиа, оповещение о сессии.", + "correct": true + }, + { "text": "Идентификация сессии, управление сессией.", "correct": true } + ] + }, + { + "text": "Задержка при передаче пакетов зависит от следующих факторов?", + "options": [ + { + "text": "Число активных сессий, пропускная способность канала.", + "correct": true + }, + { + "text": "Задержки маршрутизации, задержка при обработки очередей.", + "correct": true + }, + { + "text": "MAC задержка, переключатель контекста в ОС.", + "correct": true + } + ] + }, + { + "text": "Многие приложения используют при передаче протокол UDP, в котором нет механизмов контроля перегрузок и есть тенденция к полной остановке передачи от перегрузок канала, данная проблема решается с помощью?", + "options": [ + { "text": "Управления доступом.", "correct": true }, + { "text": "Резервированием пропускной способности.", "correct": true }, + { "text": "Механизмами управления трафиком.", "correct": true } + ] + }, + { + "text": "Пропускная способность определяет?", + "options": [ + { + "text": "Постоянно увеличивающуюся разницу между заданным и фактическим моментом появления мультимедийных объектов потока.", + "correct": false + }, + { "text": "Сколько данных может быть передано в сети.", "correct": true }, + { + "text": "Максимальную задержку передачи данных от отправителя до получателя.", + "correct": false + } + ] + }, + { + "text": "Задержка определяет?", + "options": [ + { + "text": "Постоянно увеличивающуюся разницу между заданным и фактическим моментом появления мультимедийных объектов потока.", + "correct": false + }, + { + "text": "Сколько данных может быть передано в сети.", + "correct": false + }, + { + "text": "Максимальную задержку передачи данных от отправителя до получателя.", + "correct": true + } + ] + }, + { + "text": "Сдвиг определяет?", + "options": [ + { + "text": "Постоянно увеличивающуюся разницу между заданным и фактическим моментом появления мультимедийных объектов потока.", + "correct": true + }, + { + "text": "Сколько данных может быть передано в сети.", + "correct": false + }, + { + "text": "Максимальную задержку передачи данных от отправителя до получателя.", + "correct": false + } + ] + }, + { + "text": "При маршрутизации на основе QoS?", + "options": [ + { + "text": "Решения о маршрутизации основаны не на знании топологии сети и ее метрик, а на некоторых политиках администрирования.", + "correct": false + }, + { + "text": "Маршруты вычисляются на основе нескольких ограничений.", + "correct": false + }, + { + "text": "Пути для разных потоков определяются с учетом некоторой информации о доступности ресурсов сети.", + "correct": true + } + ] + }, + { + "text": "Какие классы обслуживания вводит сущность Interserv?", + "options": [ + { "text": "Классы гарантированного сервиса.", "correct": true }, + { "text": "Классы контроля нагрузки.", "correct": true }, + { "text": "Классы негарантированной доставки.", "correct": true } + ] + }, + { + "text": "В модели сервиса Интернета - Дифференцированный сервис, каждая вершина в домене может быть?", + "options": [ + { "text": "Граничной вершиной.", "correct": true }, + { "text": "Смежной вершиной.", "correct": false }, + { "text": "Внутренней вершиной.", "correct": true } + ] + }, + { + "text": "Протоколами, которые были стандартизированы для удовлетворения различных аспектов управления сессиями являются?", + "options": [ + { "text": "SDP SAP.", "correct": true }, + { "text": "RTP, RTCP, RTSP.", "correct": false }, + { "text": "HTTP, SMTP.", "correct": false } + ] + }, + { + "text": "RTP обеспечивает функции?", + "options": [ + { + "text": "Установление аутентичности, шифрование данных в пакете IP.", + "correct": false + }, + { + "text": "Упорядочивание, идентификация передаваемой информации.", + "correct": true + }, + { + "text": "Индикация фреймов, идентификация источника, синхронизация фрагментов мультимедиа.", + "correct": true + } + ] + }, + { + "text": "Стандарт H.323 обеспечивает сервис/сервисы мультимедийных коммуникаций типа?", + "options": [ + { "text": "Point-to-multipoint.", "correct": true }, + { "text": "Point-to-point.", "correct": true }, + { "text": "Multipoint-to-multipoint.", "correct": false } + ] + }, + { + "text": "Стандарт H.323 определяет следующие компоненты, которые обеспечивают сервисы мультимедийных коммуникаций?", + "options": [ + { "text": "Гейткипер, шлюз.", "correct": true }, + { "text": "Порталл, мост.", "correct": false }, + { "text": "Терминалы, MCU.", "correct": true } + ] + }, + { + "text": "Для установки соединения и разъединения между двумя конечными элементами H.323 используется протокол?", + "options": [ + { "text": "H.245.", "correct": false }, + { "text": "H.225.", "correct": false }, + { "text": "Q.931.", "correct": true } + ] + }, + { + "text": "Для установки соединения в мультимедийной сессии SIP выполняет следующие этапы?", + "options": [ + { + "text": "Инициализация сессии, установка соединения, доставка описания сессии, управление активной сессией, завершение сессии.", + "correct": false + }, + { + "text": "Инициализация сессии, доставка описания сессии, управление активной сессией, завершение сессии.", + "correct": true + }, + { + "text": "Инициализация сессии, доставка описания сессии, управление активной сессией, контроль за сессией, завершение сессии.", + "correct": false + } + ] + }, + { + "text": "Сессия идентифицируется сетевым адресом и парами портов отправления и получения информации. По умолчанию эта пара портов?", + "options": [ + { "text": "5050 и 5051.", "correct": false }, + { "text": "5404 и 5405.", "correct": false }, + { "text": "5004 и 5005.", "correct": true } + ] + }, + { + "text": "Быстре определение принадлежности пакета к RTP-потоку за счет?", + "options": [ + { "text": "Проверке сессии RTP.", "correct": false }, + { "text": "Пакетной проверке.", "correct": true }, + { "text": "Потоковой проверке.", "correct": true } + ] + }, + { + "text": "Реализация RTCP включает в себя части?", + "options": [ + { "text": "Форматы пакетов.", "correct": true }, + { "text": "Временные правила.", "correct": true }, + { "text": "Базу данных участников.", "correct": true } + ] + }, + { + "text": "Спецификация RTCP определяет следующие стандартные форматы пакетов?", + "options": [ + { + "text": "Описание источника, формат определяемый приложением, управление участниками.", + "correct": true + }, + { "text": "Служебный, информационный.", "correct": false }, + { "text": "Отчет получателя, отчет отправителя.", "correct": true } + ] + }, + { + "text": "Информация о сессии RTCP, на основе которой происходит настройка характеристик сессии по времени, храниться в виде набора переменных?", + "options": [ + { + "text": "Пропускная способность, фрагмент пропускной способности, средний размер RTCP-пакетов.", + "correct": true + }, + { "text": "Флаг.", "correct": true }, + { + "text": "Количество участников сессии, время последней отсылки пакетов RTCP, количество переданных RTCP-пакетов и байтов данных, соответствие.", + "correct": true + } + ] + }, + { + "text": "Среднее время ожидания участником очередного пакета RTCP называется?", + "options": [ + { "text": "Задержкой.", "correct": false }, + { "text": "Отчетным интервалом.", "correct": true }, + { "text": "Временем ожидания.", "correct": false } + ] + }, + { + "text": "На среднее время ожидания влияют следующий/следующие фактор/факторы?", + "options": [ + { + "text": "Пропускная способность канала, выделенная под RTCP.", + "correct": true + }, + { + "text": "Средний размер передаваемых и получаемых RTCP-пакетов.", + "correct": true + }, + { + "text": "Общее количество участников и процент отправителей среди них.", + "correct": true + } + ] + }, + { + "text": "В начале сессии при использовании только базовых правил передачи данных каждый участник будет исходить из отсутствия на данный момент других участников сессии и соответствующим образом структурировать первый пакет RTCP. Он перешлет этот пакет в среднем через половину минимального отчетного интервала и будет строить следующий пакет на основе количества присоединившихся на данный момент участников, которых может быть несколько сотен или даже тысяч. Из-за низкого начального вычисленного количества участников сессии будет наблюдаться резкий рост нагрузки на сеть, что часто приводит к ее перегрузке. Необходимо применить процедуру?", + "options": [ + { "text": "Процедура пересмотра назад.", "correct": false }, + { "text": "Процедура пересмотра вперед.", "correct": true }, + { "text": "Процедура пересмотра пакетов BYE.", "correct": false } + ] + }, + { + "text": "При одновременном отключении большого количества пользователей возникает проблема со слишком большими задержками в отчетах оставшихся участников сессии. Эти задержки могут восприниматься как длительное молчание и пользователи в этом случае отмечаются как неактивные. Проблема решается путем: при получении каждого пакета BYE пересчитывается число участников сессии и время отсылки следующего пакета. В результате изменяется время отсылки пакетов, которое в этом случае уменьшается. Необходимо применить процедуру?", + "options": [ + { "text": "Процедура пересмотра назад.", "correct": true }, + { "text": "Процедура пересмотра вперед.", "correct": false }, + { "text": "Процедура пересмотра пакетов BYE.", "correct": false } + ] + }, + { + "text": "RTP разрешает посылать пакет BYE немедленно только в том случае, если число участников сессии менее?", + "options": [ + { "text": "25.", "correct": false }, + { "text": "30.", "correct": false }, + { "text": "50.", "correct": true } + ] + } +] diff --git a/script.js b/script.js new file mode 100644 index 0000000..560a127 --- /dev/null +++ b/script.js @@ -0,0 +1,499 @@ +document.addEventListener('DOMContentLoaded', function () { + // Элементы DOM + const startScreen = document.getElementById('start-screen'); + const quizScreen = document.getElementById('quiz-screen'); + const resultScreen = document.getElementById('result-screen'); + const reviewScreen = document.getElementById('review-screen'); + + const startBtn = document.getElementById('start-btn'); + const prevBtn = document.getElementById('prev-btn'); + const checkBtn = document.getElementById('check-btn'); + const nextBtn = document.getElementById('next-btn'); + const submitBtn = document.getElementById('submit-btn'); + const restartBtn = document.getElementById('restart-btn'); + const reviewBtn = document.getElementById('review-btn'); + const backToResultsBtn = document.getElementById('back-to-results-btn'); + + const questionElement = document.getElementById('question'); + const optionsContainer = document.getElementById('options-container'); + const selectionInfo = document.getElementById('selection-info'); + const selectedCountElement = document.getElementById('selected-count'); + const totalOptionsElement = document.getElementById('total-options'); + + const feedbackContainer = document.getElementById('feedback-container'); + const correctFeedback = document.getElementById('correct-feedback'); + const partialFeedback = document.getElementById('partially-feedback'); + const incorrectFeedback = document.getElementById('incorrect-feedback'); + const partialCorrectCount = document.getElementById('partial-correct-count'); + const totalCorrectCount = document.getElementById('total-correct-count'); + const correctAnswersText = document.getElementById('correct-answers-text'); + + const progressBar = document.querySelector('.progress-bar'); + const progressText = document.getElementById('progress-text'); + + const scoreElement = document.getElementById('score'); + const percentageElement = document.getElementById('percentage'); + const correctCountElement = document.getElementById('correct-count'); + const partialCountElement = document.getElementById('partial-count'); + const incorrectCountElement = document.getElementById('incorrect-count'); + const reviewContainer = document.getElementById('review-container'); + + // Переменные состояния + let questions = []; + let shuffledQuestions = []; + let currentQuestionIndex = 0; + let userAnswers = []; // Массив массивов выбранных индексов + let questionStatus = []; // Статус ответа на каждый вопрос: 'unanswered', 'correct', 'partial', 'incorrect' + let score = 0; + let letters = ['а', 'б', 'в', 'г', 'д', 'е']; + + // Загрузка вопросов из JSON + async function loadQuestions() { + try { + const response = await fetch('questions.json'); + if (!response.ok) { + throw new Error('Не удалось загрузить вопросы'); + } + questions = await response.json(); + console.log('Вопросы загружены:', questions.length); + } catch (error) { + console.error('Ошибка загрузки вопросов:', error); + createDemoQuestions(); + } + } + + // Создание демо-вопросов, если файл не найден + function createDemoQuestions() { + questions = [ + { + "text": "Составляющие мультимедиа могут быть разбиты на основные группы?", + "options": [ + { "text": "Текстовая, визуальная и звуковая информация.", "correct": false }, + { "text": "Текстовая, визуальная, звуковая информация и данные.", "correct": true }, + { "text": "Текстовая, визуальная, звуковая информация, данные и графическая информация.", "correct": false }, + { "text": "Только текстовая и визуальная информация.", "correct": false } + ] + }, + { + "text": "С точки зрения передачи мультимедиа могут быть классифицированы на?", + "options": [ + { "text": "Передаваемые в реальном времени.", "correct": true }, + { "text": "Передаваемые в on-line.", "correct": false }, + { "text": "Передаваемые в не реальном времени.", "correct": true }, + { "text": "Только передаваемые по запросу.", "correct": false } + ] + } + ]; + console.log('Созданы демо-вопросы с множественным выбором'); + } + + // Инициализация теста + function initializeQuiz() { + // Перемешиваем вопросы + shuffledQuestions = [...questions]; + shuffleArray(shuffledQuestions); + + // Перемешиваем варианты ответов в каждом вопросе + shuffledQuestions.forEach(question => { + shuffleArray(question.options); + + // Находим индексы правильных ответов после перемешивания + question.correctOptionIndices = []; + question.options.forEach((option, index) => { + if (option.correct) { + question.correctOptionIndices.push(index); + } + }); + + // Сохраняем текст правильных ответов для отображения + question.correctAnswersText = question.correctOptionIndices + .map(idx => `${letters[idx]}) ${question.options[idx].text}`) + .join('; '); + }); + + // Инициализируем массивы + userAnswers = new Array(shuffledQuestions.length).fill(null).map(() => []); + questionStatus = new Array(shuffledQuestions.length).fill('unanswered'); + + // Сбрасываем индекс текущего вопроса и счет + currentQuestionIndex = 0; + score = 0; + + // Обновляем UI + updateProgress(); + showQuestion(); + } + + // Перемешивание массива (алгоритм Фишера-Йетса) + function 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; + } + + // Показать вопрос + function showQuestion() { + const question = shuffledQuestions[currentQuestionIndex]; + questionElement.textContent = `${currentQuestionIndex + 1}. ${question.text}`; + + // Обновляем информацию о выборе + totalOptionsElement.textContent = question.options.length; + updateSelectionInfo(); + + // Скрываем блок обратной связи, если вопрос еще не проверен + if (questionStatus[currentQuestionIndex] === 'unanswered') { + feedbackContainer.classList.add('hidden'); + } else { + showFeedback(); + } + + // Очищаем контейнер с вариантами + optionsContainer.innerHTML = ''; + + // Создаем варианты ответов + question.options.forEach((option, index) => { + const optionElement = document.createElement('div'); + optionElement.className = 'option'; + + // Проверяем, выбран ли этот вариант + const isSelected = userAnswers[currentQuestionIndex].includes(index); + const isChecked = questionStatus[currentQuestionIndex] !== 'unanswered'; + + if (isSelected) { + optionElement.classList.add('selected'); + } + + // Если вопрос проверен, показываем правильность ответов + if (isChecked) { + optionElement.classList.add('checked'); + + const isCorrectOption = question.correctOptionIndices.includes(index); + + if (isSelected && isCorrectOption) { + optionElement.classList.add('correct'); + } else if (isSelected && !isCorrectOption) { + optionElement.classList.add('incorrect'); + } else if (!isSelected && isCorrectOption) { + optionElement.classList.add('correct'); + } + } + + optionElement.innerHTML = ` +
+
${letters[index]}) ${option.text}
+ `; + + // Если вопрос еще не проверен, добавляем обработчик клика + if (!isChecked) { + optionElement.addEventListener('click', () => toggleOption(index)); + } + + optionsContainer.appendChild(optionElement); + }); + + // Обновляем состояние кнопок навигации + updateNavigationButtons(); + } + + // Переключить выбор варианта + function toggleOption(optionIndex) { + const currentAnswers = userAnswers[currentQuestionIndex]; + const index = currentAnswers.indexOf(optionIndex); + + if (index === -1) { + // Добавляем вариант + currentAnswers.push(optionIndex); + } else { + // Удаляем вариант + currentAnswers.splice(index, 1); + } + + // Обновляем отображение + updateSelectionInfo(); + showQuestion(); + } + + // Обновить информацию о выбранных вариантах + function updateSelectionInfo() { + const selectedCount = userAnswers[currentQuestionIndex].length; + selectedCountElement.textContent = selectedCount; + + // Подсвечиваем информацию в зависимости от количества выбранных ответов + if (selectedCount === 0) { + selectionInfo.style.borderColor = '#e0e0e0'; + selectionInfo.style.backgroundColor = '#f0f4ff'; + } else { + selectionInfo.style.borderColor = '#4a00e0'; + selectionInfo.style.backgroundColor = '#e6f7ff'; + } + } + + // Обновить кнопки навигации + function updateNavigationButtons() { + const isAnswered = questionStatus[currentQuestionIndex] !== 'unanswered'; + const hasSelection = userAnswers[currentQuestionIndex].length > 0; + + prevBtn.disabled = currentQuestionIndex === 0; + + if (isAnswered) { + checkBtn.classList.add('hidden'); + nextBtn.classList.remove('hidden'); + } else { + checkBtn.classList.remove('hidden'); + nextBtn.classList.add('hidden'); + checkBtn.disabled = !hasSelection; + } + + // Показываем кнопку завершения на последнем проверенном вопросе + const allQuestionsAnswered = questionStatus.every(status => status !== 'unanswered'); + const isLastQuestion = currentQuestionIndex === shuffledQuestions.length - 1; + + if (isLastQuestion && isAnswered) { + nextBtn.classList.add('hidden'); + submitBtn.classList.remove('hidden'); + } else { + submitBtn.classList.add('hidden'); + } + } + + // Проверить ответ и показать результат + function checkAnswer() { + const question = shuffledQuestions[currentQuestionIndex]; + const userSelected = userAnswers[currentQuestionIndex]; + const correctIndices = question.correctOptionIndices; + + // Находим количество правильных выбранных ответов + const correctSelected = userSelected.filter(idx => correctIndices.includes(idx)).length; + const incorrectSelected = userSelected.length - correctSelected; + const missedCorrect = correctIndices.length - correctSelected; + + // Определяем статус ответа + let status; + let feedbackType; + + if (incorrectSelected === 0 && missedCorrect === 0) { + // Все правильные выбраны, ничего лишнего + status = 'correct'; + feedbackType = 'correct'; + score += 1; + } else if (correctSelected > 0 && (incorrectSelected > 0 || missedCorrect > 0)) { + // Частично правильно + status = 'partial'; + feedbackType = 'partial'; + score += 0.5; // Половина балла за частично правильный ответ + } else { + // Полностью неправильно + status = 'incorrect'; + feedbackType = 'incorrect'; + } + + // Сохраняем статус вопроса + questionStatus[currentQuestionIndex] = status; + + // Показываем обратную связь + showFeedback(correctSelected, correctIndices.length); + + // Обновляем навигацию + updateNavigationButtons(); + + // Обновляем отображение вариантов + showQuestion(); + } + + // Показать обратную связь + function showFeedback(correctSelected = 0, totalCorrect = 0) { + const question = shuffledQuestions[currentQuestionIndex]; + const status = questionStatus[currentQuestionIndex]; + + // Скрываем все виды обратной связи + correctFeedback.classList.add('hidden'); + partialFeedback.classList.add('hidden'); + incorrectFeedback.classList.add('hidden'); + + // Показываем соответствующий вид обратной связи + switch (status) { + case 'correct': + correctFeedback.classList.remove('hidden'); + break; + case 'partial': + partialFeedback.classList.remove('hidden'); + partialCorrectCount.textContent = correctSelected; + totalCorrectCount.textContent = totalCorrect; + break; + case 'incorrect': + incorrectFeedback.classList.remove('hidden'); + correctAnswersText.textContent = question.correctAnswersText; + break; + } + + feedbackContainer.classList.remove('hidden'); + } + + // Обновление прогресса + function updateProgress() { + const progress = ((currentQuestionIndex + 1) / shuffledQuestions.length) * 100; + progressBar.style.setProperty('--width', `${progress}%`); + progressText.textContent = `Вопрос ${currentQuestionIndex + 1} из ${shuffledQuestions.length}`; + } + + // Переход к следующему вопросу + function nextQuestion() { + if (currentQuestionIndex < shuffledQuestions.length - 1) { + currentQuestionIndex++; + updateProgress(); + showQuestion(); + } + } + + // Переход к предыдущему вопросу + function prevQuestion() { + if (currentQuestionIndex > 0) { + currentQuestionIndex--; + updateProgress(); + showQuestion(); + } + } + + // Завершение теста и подсчет результатов + function finishQuiz() { + // Подсчет детальной статистики + let correctCount = 0; + let partialCount = 0; + let incorrectCount = 0; + + questionStatus.forEach(status => { + switch (status) { + case 'correct': + correctCount++; + break; + case 'partial': + partialCount++; + break; + case 'incorrect': + incorrectCount++; + break; + } + }); + + // Обновление экрана результатов + scoreElement.textContent = Math.round(score * 10) / 10; // Округляем до одного знака после запятой + const percentage = Math.round((score / shuffledQuestions.length) * 100); + percentageElement.textContent = `${percentage}%`; + correctCountElement.textContent = correctCount; + partialCountElement.textContent = partialCount; + incorrectCountElement.textContent = incorrectCount; + + // Обновление кругового индикатора + const circle = document.querySelector('.circle'); + circle.style.background = `conic-gradient(#4a00e0 ${percentage}%, #e0e0e0 ${percentage}%)`; + + // Переключение экранов + quizScreen.classList.add('hidden'); + resultScreen.classList.remove('hidden'); + } + + // Показать обзор ответов + function showReview() { + reviewContainer.innerHTML = ''; + + shuffledQuestions.forEach((question, index) => { + const reviewItem = document.createElement('div'); + reviewItem.className = 'review-item'; + + const status = questionStatus[index]; + const userSelected = userAnswers[index]; + const correctIndices = question.correctOptionIndices; + + // Определяем статус для отображения + let statusText, statusClass; + switch (status) { + case 'correct': + statusText = 'Правильно'; + statusClass = 'status-correct'; + break; + case 'partial': + statusText = 'Частично правильно'; + statusClass = 'status-partial'; + break; + case 'incorrect': + statusText = 'Неправильно'; + statusClass = 'status-incorrect'; + break; + default: + statusText = 'Не отвечено'; + statusClass = 'status-incorrect'; + } + + let optionsHTML = ''; + question.options.forEach((option, optionIndex) => { + let optionClass = ''; + const isCorrect = correctIndices.includes(optionIndex); + const isSelected = userSelected.includes(optionIndex); + + if (isCorrect && isSelected) { + optionClass = 'user-correct'; + } else if (isCorrect && !isSelected) { + optionClass = 'correct-answer'; + } else if (!isCorrect && isSelected) { + optionClass = 'user-incorrect'; + } else if (status === 'partial' && isSelected && !isCorrect) { + optionClass = 'user-partial'; + } + + optionsHTML += ` +
+ ${letters[optionIndex]}) ${option.text} +
+ `; + }); + + reviewItem.innerHTML = ` +
+ ${index + 1}. ${question.text} + ${statusText} +
+
+ ${optionsHTML} +
+
+ Ваши ответы: ${userSelected.length > 0 + ? userSelected.map(idx => letters[idx]).join(', ') + : 'нет ответа'} +
+ `; + + reviewContainer.appendChild(reviewItem); + }); + + resultScreen.classList.add('hidden'); + reviewScreen.classList.remove('hidden'); + } + + // Начало теста + function startQuiz() { + startScreen.classList.add('hidden'); + quizScreen.classList.remove('hidden'); + initializeQuiz(); + } + + // События + startBtn.addEventListener('click', startQuiz); + prevBtn.addEventListener('click', prevQuestion); + checkBtn.addEventListener('click', checkAnswer); + nextBtn.addEventListener('click', nextQuestion); + submitBtn.addEventListener('click', finishQuiz); + restartBtn.addEventListener('click', () => { + resultScreen.classList.add('hidden'); + startScreen.classList.remove('hidden'); + }); + reviewBtn.addEventListener('click', showReview); + backToResultsBtn.addEventListener('click', () => { + reviewScreen.classList.add('hidden'); + resultScreen.classList.remove('hidden'); + }); + + // Загружаем вопросы при загрузке страницы + loadQuestions(); +}); \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..6f5041e --- /dev/null +++ b/style.css @@ -0,0 +1,732 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; +} + +body { + background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); + min-height: 100vh; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; +} + +.container { + max-width: 1000px; + width: 100%; + background-color: white; + border-radius: 20px; + box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2); + overflow: hidden; +} + +header { + background: linear-gradient(to right, #4a00e0, #8e2de2); + color: white; + padding: 25px 30px; + text-align: center; +} + +header h1 { + font-size: 2.2rem; + margin-bottom: 10px; + display: flex; + align-items: center; + justify-content: center; + gap: 15px; +} + +header .subtitle { + font-size: 1.1rem; + opacity: 0.9; + margin-bottom: 15px; +} + +.mode-indicator { + background-color: rgba(255, 255, 255, 0.15); + border-radius: 10px; + padding: 12px 20px; + margin-top: 15px; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 5px; +} + +.mode-tag { + background-color: #ff9800; + color: white; + padding: 5px 15px; + border-radius: 20px; + font-weight: 600; + font-size: 0.9rem; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.mode-info { + font-size: 0.9rem; + opacity: 0.9; +} + +.quiz-container { + padding: 30px; +} + +.start-screen, +.quiz-screen, +.result-screen, +.review-screen { + transition: all 0.5s ease; +} + +.hidden { + display: none !important; +} + +.start-content { + text-align: center; + padding: 20px 0; +} + +.start-content h2 { + color: #333; + margin-bottom: 25px; + font-size: 1.8rem; +} + +.start-content ul { + text-align: left; + max-width: 700px; + margin: 0 auto 30px; + background-color: #f8f9fa; + padding: 25px; + border-radius: 15px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); +} + +.start-content li { + margin-bottom: 15px; + font-size: 1.1rem; + display: flex; + align-items: flex-start; + gap: 12px; +} + +.start-content li i { + color: #4a00e0; + margin-top: 3px; +} + +.btn { + background: linear-gradient(to right, #4a00e0, #8e2de2); + color: white; + border: none; + padding: 15px 30px; + border-radius: 50px; + font-size: 1.1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + box-shadow: 0 5px 15px rgba(74, 0, 224, 0.3); +} + +.btn:hover { + transform: translateY(-3px); + box-shadow: 0 8px 20px rgba(74, 0, 224, 0.4); +} + +.btn:active { + transform: translateY(0); +} + +.btn:disabled { + background: #cccccc; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.check-btn { + background: linear-gradient(to right, #ff9800, #ff5722); +} + +.check-btn:hover { + box-shadow: 0 8px 20px rgba(255, 87, 34, 0.4); +} + +.start-btn { + padding: 18px 45px; + font-size: 1.2rem; +} + +.progress-container { + margin-bottom: 30px; +} + +.progress-bar { + height: 10px; + background-color: #e0e0e0; + border-radius: 5px; + overflow: hidden; + margin-bottom: 10px; + position: relative; +} + +.progress-bar::after { + content: ""; + display: block; + height: 100%; + width: var(--width, 0%); + background: linear-gradient(to right, #4a00e0, #8e2de2); + transition: width 0.5s ease; + position: absolute; + top: 0; + left: 0; +} + +.progress-text { + text-align: right; + font-size: 1rem; + color: #666; +} + +.question-container { + margin-bottom: 30px; +} + +.question-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 15px; +} + +.question-header h2 { + color: #333; + font-size: 1.5rem; + line-height: 1.5; + flex: 1; + min-width: 300px; +} + +.selection-info { + background-color: #f0f4ff; + padding: 10px 20px; + border-radius: 25px; + font-size: 1rem; + color: #4a00e0; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; + border: 2px solid #e0e0e0; +} + +.selection-info i { + font-size: 1.2rem; +} + +/* Блок обратной связи */ +.feedback-container { + margin-bottom: 20px; + animation: fadeIn 0.5s ease; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.feedback { + display: flex; + align-items: flex-start; + gap: 15px; + font-size: 1.1rem; + padding: 20px; + border-radius: 12px; + border-left: 5px solid; +} + +.feedback i { + font-size: 1.8rem; + margin-top: 2px; +} + +.feedback-content { + display: flex; + flex-direction: column; + gap: 5px; +} + +.feedback-title { + font-weight: 700; + font-size: 1.2rem; +} + +.feedback-detail { + opacity: 0.9; + font-size: 1rem; +} + +.correct-feedback { + background-color: #e7f7ef; + color: #2e7d32; + border-left-color: #2e7d32; +} + +.partially-feedback { + background-color: #fff8e1; + color: #f57c00; + border-left-color: #ff9800; +} + +.incorrect-feedback { + background-color: #fdeaea; + color: #c62828; + border-left-color: #c62828; +} + +.options-container { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 20px; +} + +.option { + background-color: #f8f9fa; + border: 2px solid #e0e0e0; + border-radius: 12px; + padding: 18px 20px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 15px; + font-size: 1.1rem; +} + +.option:hover { + background-color: #eef2ff; + border-color: #8e2de2; +} + +.option.selected { + background-color: #e6f7ff; + border-color: #4a00e0; + box-shadow: 0 3px 8px rgba(74, 0, 224, 0.1); +} + +.option.correct { + background-color: #e7f7ef; + border-color: #2e7d32; + color: #2e7d32; +} + +.option.partial { + background-color: #fff8e1; + border-color: #ff9800; + color: #f57c00; +} + +.option.incorrect { + background-color: #fdeaea; + border-color: #c62828; + color: #c62828; +} + +.option.checked { + cursor: default; +} + +.option.checked:hover { + background-color: inherit; + border-color: inherit; +} + +.option-checkbox { + width: 24px; + height: 24px; + border: 2px solid #4a00e0; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: all 0.2s ease; +} + +.option.selected .option-checkbox { + background-color: #4a00e0; + color: white; +} + +.option.selected .option-checkbox::after { + content: "✓"; + font-weight: bold; +} + +.option.correct .option-checkbox { + background-color: #2e7d32; + border-color: #2e7d32; +} + +.option.partial .option-checkbox { + background-color: #ff9800; + border-color: #ff9800; +} + +.option.incorrect .option-checkbox { + background-color: #c62828; + border-color: #c62828; +} + +.option-text { + flex: 1; +} + +.question-hint { + background-color: #f0f4ff; + padding: 12px 20px; + border-radius: 10px; + color: #4a00e0; + font-size: 1rem; + display: flex; + align-items: center; + gap: 10px; + border-left: 4px solid #4a00e0; +} + +.navigation { + display: flex; + justify-content: space-between; + margin-top: 30px; + flex-wrap: wrap; + gap: 15px; +} + +.prev-btn, +.check-btn, +.next-btn, +.submit-btn { + min-width: 180px; +} + +.result-content { + text-align: center; + padding: 20px 0; +} + +.result-content h2 { + color: #333; + margin-bottom: 25px; + font-size: 1.8rem; +} + +.score-circle { + margin: 30px auto; + max-width: 300px; +} + +.circle { + width: 200px; + height: 200px; + border-radius: 50%; + background: conic-gradient(#4a00e0 0%, #e0e0e0 0%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin: 0 auto 15px; + position: relative; +} + +.circle::before { + content: ""; + position: absolute; + width: 170px; + height: 170px; + background-color: white; + border-radius: 50%; +} + +.circle span { + font-size: 3.5rem; + font-weight: 700; + color: #333; + z-index: 1; +} + +.score-label { + font-size: 1rem; + color: #666; + z-index: 1; + margin-top: 5px; +} + +.percentage { + font-size: 1.8rem; + font-weight: 700; + color: #4a00e0; +} + +.result-details { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + margin: 30px 0; +} + +.result-item { + display: flex; + align-items: center; + gap: 12px; + padding: 15px 25px; + border-radius: 12px; + font-size: 1.2rem; + min-width: 250px; +} + +.result-item i { + font-size: 1.5rem; +} + +.correct { + background-color: #e7f7ef; + color: #2e7d32; +} + +.partially { + background-color: #fff8e1; + color: #f57c00; +} + +.incorrect { + background-color: #fdeaea; + color: #c62828; +} + +.result-actions { + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + margin-top: 30px; +} + +.restart-btn, +.review-btn { + min-width: 220px; +} + +.review-header { + text-align: center; + margin-bottom: 30px; +} + +.review-header h2 { + color: #333; + margin-bottom: 15px; +} + +.review-header p { + color: #666; + font-size: 1.1rem; + line-height: 1.6; +} + +.correct-badge, +.partial-badge, +.incorrect-badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 12px; + border-radius: 15px; + font-size: 0.9rem; + margin: 0 5px; +} + +.correct-badge { + background-color: #e7f7ef; + color: #2e7d32; +} + +.partial-badge { + background-color: #fff8e1; + color: #f57c00; +} + +.incorrect-badge { + background-color: #fdeaea; + color: #c62828; +} + +.review-item { + background-color: #f8f9fa; + border-radius: 15px; + padding: 25px; + margin-bottom: 20px; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.05); +} + +.review-question { + font-size: 1.2rem; + margin-bottom: 20px; + color: #333; + font-weight: 600; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.question-status { + font-size: 1rem; + padding: 5px 15px; + border-radius: 15px; + font-weight: 600; +} + +.status-correct { + background-color: #e7f7ef; + color: #2e7d32; +} + +.status-partial { + background-color: #fff8e1; + color: #f57c00; +} + +.status-incorrect { + background-color: #fdeaea; + color: #c62828; +} + +.review-options { + display: flex; + flex-direction: column; + gap: 10px; +} + +.review-option { + padding: 12px 15px; + border-radius: 8px; + display: flex; + align-items: center; + gap: 10px; + border-left: 4px solid transparent; +} + +.correct-answer { + background-color: #e7f7ef; + border-left-color: #2e7d32; +} + +.user-correct { + background-color: #e7f7ef; + border-left-color: #2e7d32; +} + +.user-partial { + background-color: #fff8e1; + border-left-color: #ff9800; +} + +.user-incorrect { + background-color: #fdeaea; + border-left-color: #c62828; +} + +.review-actions { + display: flex; + justify-content: center; + margin-top: 30px; +} + +footer { + background-color: #f8f9fa; + padding: 20px; + text-align: center; + color: #666; + font-size: 0.9rem; + border-top: 1px solid #e0e0e0; +} + +@media (max-width: 768px) { + .container { + border-radius: 15px; + } + + header h1 { + font-size: 1.8rem; + } + + .quiz-container { + padding: 20px; + } + + .question-header { + flex-direction: column; + align-items: flex-start; + } + + .question-header h2 { + min-width: auto; + } + + .navigation { + flex-direction: column; + align-items: stretch; + } + + .prev-btn, + .check-btn, + .next-btn, + .submit-btn { + width: 100%; + min-width: auto; + } + + .result-details { + flex-direction: column; + align-items: center; + } + + .result-item { + width: 100%; + max-width: 300px; + min-width: auto; + } + + .result-actions { + flex-direction: column; + align-items: center; + } + + .restart-btn, + .review-btn { + width: 100%; + max-width: 300px; + min-width: auto; + } +}