This commit is contained in:
2026-02-14 15:31:14 +03:00
commit 9a68ecccfc
5 changed files with 1811 additions and 0 deletions

22
favicon.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<!-- Градиентный фон -->
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#667eea" />
<stop offset="100%" stop-color="#764ba2" />
</linearGradient>
</defs>
<!-- Круглый фон с градиентом -->
<circle cx="50" cy="50" r="45" fill="url(#gradient)" />
<!-- Абстрактный узор в центре -->
<path d="M50 30 L65 50 L50 70 L35 50 Z" fill="white" opacity="0.9" />
<circle cx="50" cy="50" r="15" fill="none" stroke="white" stroke-width="3" opacity="0.8" />
<!-- Декоративные точки -->
<circle cx="30" cy="30" r="4" fill="white" opacity="0.7" />
<circle cx="70" cy="30" r="3" fill="white" opacity="0.7" />
<circle cx="70" cy="70" r="4" fill="white" opacity="0.7" />
<circle cx="30" cy="70" r="3" fill="white" opacity="0.7" />
</svg>

After

Width:  |  Height:  |  Size: 964 B

226
index.html Normal file
View File

@@ -0,0 +1,226 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Тестирование по мультимедиа</title>
<link rel="stylesheet" href="style.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
</head>
<body>
<div class="container">
<header>
<h1>
<i class="fas fa-question-circle"></i> Тестирование по мультимедиа
</h1>
<p class="subtitle">
Проверьте свои знания в области мультимедийных технологий
</p>
</header>
<div class="quiz-container" id="quiz-container">
<div class="start-screen" id="start-screen">
<div class="start-content">
<h2>Инструкция к тесту</h2>
<ul>
<li>
<i class="fas fa-check-circle"></i> Всего 30 вопросов по теме
мультимедиа
</li>
<li>
<i class="fas fa-random"></i> Вопросы и варианты ответов
перемешиваются
</li>
<li>
<i class="fas fa-clock"></i> На прохождение теста нет
ограничений по времени
</li>
<li>
<i class="fas fa-check-double"></i> Можно выбирать несколько
вариантов ответа
</li>
<li>
<i class="fas fa-arrow-right"></i> Проверка происходит при
нажатии "Следующий"
</li>
<li>
<i class="fas fa-star"></i> Полный балл за вопрос - если выбраны
все правильные ответы
</li>
<li>
<i class="fas fa-chart-bar"></i> Результат будет показан после
завершения теста
</li>
</ul>
<button id="start-btn" class="btn start-btn">
<i class="fas fa-play"></i> Начать тест
</button>
</div>
</div>
<div class="quiz-screen hidden" id="quiz-screen">
<div class="progress-container">
<div class="progress-bar" id="progress-bar"></div>
<div class="progress-text" id="progress-text">Вопрос 0 из 0</div>
</div>
<div class="question-container">
<div class="question-header">
<h2 id="question">Текст вопроса появится здесь</h2>
<div class="selection-info" id="selection-info">
<i class="fas fa-mouse-pointer"></i> Выбрано:
<span id="selected-count">0</span> из
<span id="total-options">0</span>
</div>
</div>
<!-- Блок обратной связи -->
<div class="feedback-container hidden" id="feedback-container">
<div class="feedback correct-feedback" id="correct-feedback">
<i class="fas fa-check-circle"></i>
<div class="feedback-content">
<span class="feedback-title"
>Правильно! Отличная работа!</span
>
<span class="feedback-detail"
>Вы выбрали все правильные ответы</span
>
</div>
</div>
<div class="feedback partially-feedback" id="partially-feedback">
<i class="fas fa-exclamation-circle"></i>
<div class="feedback-content">
<span class="feedback-title">Частично правильно</span>
<span class="feedback-detail"
>Вы выбрали <span id="partial-correct-count">0</span> из
<span id="total-correct-count">0</span> правильных
ответов</span
>
</div>
</div>
<div class="feedback incorrect-feedback" id="incorrect-feedback">
<i class="fas fa-times-circle"></i>
<div class="feedback-content">
<span class="feedback-title">Неправильно</span>
<span class="feedback-detail"
>Правильные ответы: <span id="correct-answers-text"></span
></span>
</div>
</div>
</div>
<div class="options-container" id="options-container">
<!-- Варианты ответов будут здесь -->
</div>
<div class="question-hint">
<i class="fas fa-info-circle"></i> Выберите один или несколько
вариантов ответа
</div>
</div>
<div class="navigation">
<button id="prev-btn" class="btn prev-btn" disabled>
<i class="fas fa-arrow-left"></i> Предыдущий
</button>
<button id="check-btn" class="btn check-btn">
<i class="fas fa-check"></i> Проверить и продолжить
</button>
<button id="next-btn" class="btn next-btn hidden">
Следующий <i class="fas fa-arrow-right"></i>
</button>
<button id="submit-btn" class="btn submit-btn hidden">
<i class="fas fa-paper-plane"></i> Завершить тест
</button>
</div>
</div>
<div class="result-screen hidden" id="result-screen">
<div class="result-content">
<h2>Результаты тестирования</h2>
<div class="score-circle">
<div class="circle">
<span id="score">0</span>
<div class="score-label">из 30</div>
</div>
<div class="percentage" id="percentage">0%</div>
</div>
<div class="result-details">
<div class="result-item correct">
<i class="fas fa-check"></i>
<span
>Правильные ответы:
<strong id="correct-count">0</strong></span
>
</div>
<div class="result-item partially">
<i class="fas fa-exclamation"></i>
<span
>Частично правильные:
<strong id="partial-count">0</strong></span
>
</div>
<div class="result-item incorrect">
<i class="fas fa-times"></i>
<span
>Неправильные ответы:
<strong id="incorrect-count">0</strong></span
>
</div>
</div>
<div class="result-actions">
<button id="restart-btn" class="btn restart-btn">
<i class="fas fa-redo"></i> Пройти тест снова
</button>
<button id="review-btn" class="btn review-btn">
<i class="fas fa-eye"></i> Просмотреть ответы
</button>
</div>
</div>
</div>
<div class="review-screen hidden" id="review-screen">
<div class="review-header">
<h2>Просмотр ответов</h2>
<p>
<span class="correct-badge"
><i class="fas fa-check"></i> Зеленый</span
>
- правильные ответы,
<span class="partial-badge"
><i class="fas fa-exclamation"></i> Желтый</span
>
- выбранные вами ответы,
<span class="incorrect-badge"
><i class="fas fa-times"></i> Красный</span
>
- неправильные выбранные ответы
</p>
</div>
<div class="review-container" id="review-container">
<!-- Обзор вопросов будет здесь -->
</div>
<div class="review-actions">
<button id="back-to-results-btn" class="btn">
<i class="fas fa-arrow-left"></i> Назад к результатам
</button>
</div>
</div>
</div>
<footer>
<p>Гарантия верности ответов не даётся!</p>
</footer>
</div>
<script src="script.js"></script>
</body>
</html>

332
questions.json Normal file
View File

@@ -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 }
]
}
]

499
script.js Normal file
View File

@@ -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 = `
<div class="option-checkbox"></div>
<div class="option-text">${letters[index]}) ${option.text}</div>
`;
// Если вопрос еще не проверен, добавляем обработчик клика
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 += `
<div class="review-option ${optionClass}">
<strong>${letters[optionIndex]})</strong> ${option.text}
</div>
`;
});
reviewItem.innerHTML = `
<div class="review-question">
<span>${index + 1}. ${question.text}</span>
<span class="question-status ${statusClass}">${statusText}</span>
</div>
<div class="review-options">
${optionsHTML}
</div>
<div style="margin-top: 15px; font-style: italic;">
Ваши ответы: ${userSelected.length > 0
? userSelected.map(idx => letters[idx]).join(', ')
: 'нет ответа'}
</div>
`;
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();
});

732
style.css Normal file
View File

@@ -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;
}
}