|
|
@@ -0,0 +1,527 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="ru">
|
|
|
+<head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>Агент Secure Print — игра по безопасности печати</title>
|
|
|
+ <style>
|
|
|
+ * {
|
|
|
+ box-sizing: border-box;
|
|
|
+ user-select: none;
|
|
|
+ }
|
|
|
+ body {
|
|
|
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
|
+ background: linear-gradient(135deg, #1a2a3a, #0d1c2a);
|
|
|
+ min-height: 100vh;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ margin: 0;
|
|
|
+ padding: 20px;
|
|
|
+ }
|
|
|
+ .game-container {
|
|
|
+ max-width: 800px;
|
|
|
+ width: 100%;
|
|
|
+ background: #f5f3ef;
|
|
|
+ border-radius: 32px;
|
|
|
+ box-shadow: 0 20px 40px rgba(0,0,0,0.4);
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+ .scene {
|
|
|
+ padding: 30px 25px;
|
|
|
+ }
|
|
|
+ .scene-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: baseline;
|
|
|
+ border-bottom: 2px solid #d0b87a;
|
|
|
+ margin-bottom: 25px;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ }
|
|
|
+ .scene-title {
|
|
|
+ font-size: 1.8rem;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #2c3e2f;
|
|
|
+ }
|
|
|
+ .score {
|
|
|
+ background: #2c3e2f;
|
|
|
+ color: #f9e0a8;
|
|
|
+ padding: 6px 14px;
|
|
|
+ border-radius: 40px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ .scene-content {
|
|
|
+ min-height: 380px;
|
|
|
+ }
|
|
|
+ .question-block {
|
|
|
+ background: white;
|
|
|
+ border-radius: 24px;
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
|
+ border: 1px solid #e0cfb0;
|
|
|
+ }
|
|
|
+ .question-text {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 1.2rem;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ color: #2c3e2f;
|
|
|
+ background: #f0eadb;
|
|
|
+ padding: 15px;
|
|
|
+ border-radius: 16px;
|
|
|
+ }
|
|
|
+ .answers {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .answer-option {
|
|
|
+ background: #f9f7f2;
|
|
|
+ padding: 14px 18px;
|
|
|
+ border-radius: 16px;
|
|
|
+ cursor: pointer;
|
|
|
+ border: 2px solid #e0cfb0;
|
|
|
+ transition: all 0.1s;
|
|
|
+ font-size: 1rem;
|
|
|
+ }
|
|
|
+ .answer-option:hover {
|
|
|
+ background: #e8dfcc;
|
|
|
+ border-color: #b98f4a;
|
|
|
+ transform: scale(1.01);
|
|
|
+ }
|
|
|
+ .element {
|
|
|
+ background: white;
|
|
|
+ border-radius: 24px;
|
|
|
+ padding: 20px;
|
|
|
+ margin-bottom: 20px;
|
|
|
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
|
+ cursor: pointer;
|
|
|
+ border: 1px solid #e0cfb0;
|
|
|
+ }
|
|
|
+ .element:hover {
|
|
|
+ background: #fffaf2;
|
|
|
+ border-color: #b98f4a;
|
|
|
+ }
|
|
|
+ .element-title {
|
|
|
+ font-weight: bold;
|
|
|
+ font-size: 1.3rem;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ color: #a45d2e;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+ .actions {
|
|
|
+ margin-top: 20px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 15px;
|
|
|
+ }
|
|
|
+ button {
|
|
|
+ background: #2c5f2d;
|
|
|
+ border: none;
|
|
|
+ color: white;
|
|
|
+ font-weight: bold;
|
|
|
+ padding: 12px 24px;
|
|
|
+ border-radius: 40px;
|
|
|
+ font-size: 1rem;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: 0.1s;
|
|
|
+ }
|
|
|
+ button:hover {
|
|
|
+ background: #1f4520;
|
|
|
+ transform: scale(0.97);
|
|
|
+ }
|
|
|
+ .message {
|
|
|
+ background: #e9e3d5;
|
|
|
+ border-left: 6px solid #b98f4a;
|
|
|
+ padding: 12px;
|
|
|
+ margin-top: 20px;
|
|
|
+ border-radius: 16px;
|
|
|
+ font-style: italic;
|
|
|
+ }
|
|
|
+ .game-over {
|
|
|
+ background: #8b3c2c;
|
|
|
+ color: white;
|
|
|
+ text-align: center;
|
|
|
+ padding: 30px;
|
|
|
+ border-radius: 20px;
|
|
|
+ }
|
|
|
+ .win {
|
|
|
+ background: #2c5f2d;
|
|
|
+ }
|
|
|
+ footer {
|
|
|
+ background: #e0cfb0;
|
|
|
+ padding: 12px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 0.8rem;
|
|
|
+ color: #2c3e2f;
|
|
|
+ }
|
|
|
+ .feedback {
|
|
|
+ margin-top: 15px;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+ .feedback-correct {
|
|
|
+ background: #c8e6c9;
|
|
|
+ color: #1b5e20;
|
|
|
+ border-left: 4px solid #2e7d32;
|
|
|
+ }
|
|
|
+ .feedback-wrong {
|
|
|
+ background: #ffcdd2;
|
|
|
+ color: #c62828;
|
|
|
+ border-left: 4px solid #d32f2f;
|
|
|
+ }
|
|
|
+ hr {
|
|
|
+ margin: 15px 0;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div class="game-container" id="gameRoot"></div>
|
|
|
+
|
|
|
+<script>
|
|
|
+ // СОСТОЯНИЕ ИГРЫ
|
|
|
+ let currentScene = 1;
|
|
|
+ let score = 0;
|
|
|
+ let gameActive = true;
|
|
|
+ let finalMessage = "";
|
|
|
+ let waitingForNext = false;
|
|
|
+
|
|
|
+ function render() {
|
|
|
+ if (!gameActive) {
|
|
|
+ const root = document.getElementById('gameRoot');
|
|
|
+ root.innerHTML = `
|
|
|
+ <div class="scene">
|
|
|
+ <div class="game-over ${finalMessage.includes('ПОБЕДА') ? 'win' : ''}">
|
|
|
+ <h2>🔐 ${finalMessage.includes('ПОБЕДА') ? 'ПОБЕДА!' : 'ИГРА ОКОНЧЕНА'}</h2>
|
|
|
+ <p>${finalMessage}</p>
|
|
|
+ <p><strong>Итоговый счёт безопасности: ${score}</strong></p>
|
|
|
+ <button onclick="resetGame()">🔄 Начать заново</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const sceneData = getSceneData(currentScene);
|
|
|
+ if (!sceneData) return;
|
|
|
+
|
|
|
+ const root = document.getElementById('gameRoot');
|
|
|
+
|
|
|
+ let contentHtml = '';
|
|
|
+
|
|
|
+ if (sceneData.type === 'quiz') {
|
|
|
+ // Формат с вариантами ответов (как в требовании вариант №1)
|
|
|
+ contentHtml = `
|
|
|
+ <div class="question-block">
|
|
|
+ <div class="question-text">❓ ${sceneData.question}</div>
|
|
|
+ <div class="answers" id="answersContainer">
|
|
|
+ ${sceneData.answers.map((ans, idx) => `
|
|
|
+ <div class="answer-option" data-answer-index="${idx}">
|
|
|
+ ${ans.text}
|
|
|
+ </div>
|
|
|
+ `).join('')}
|
|
|
+ </div>
|
|
|
+ <div id="quizFeedback" class="feedback" style="display:none;"></div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ } else {
|
|
|
+ // Обычная сцена с элементами
|
|
|
+ let elementsHtml = '';
|
|
|
+ for (let el of sceneData.elements || []) {
|
|
|
+ elementsHtml += `
|
|
|
+ <div class="element" data-element-id="${el.id}">
|
|
|
+ <div class="element-title">
|
|
|
+ <span>📄 ${el.title}</span>
|
|
|
+ </div>
|
|
|
+ <div class="element-desc">${el.desc}</div>
|
|
|
+ </div>
|
|
|
+ `;
|
|
|
+ }
|
|
|
+ contentHtml = elementsHtml;
|
|
|
+ }
|
|
|
+
|
|
|
+ let actionsHtml = '';
|
|
|
+ for (let act of sceneData.actions || []) {
|
|
|
+ actionsHtml += `<button data-action-id="${act.id}">${act.label}</button>`;
|
|
|
+ }
|
|
|
+
|
|
|
+ const html = `
|
|
|
+ <div class="scene">
|
|
|
+ <div class="scene-header">
|
|
|
+ <div class="scene-title">${sceneData.title}</div>
|
|
|
+ <div class="score">🔒 Безопасность: ${score}</div>
|
|
|
+ </div>
|
|
|
+ <div class="scene-content">
|
|
|
+ ${contentHtml}
|
|
|
+ ${actionsHtml ? `<div class="actions">${actionsHtml}</div>` : ''}
|
|
|
+ <div class="message" id="sceneMessage">${sceneData.defaultMessage || 'Выберите действие...'}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <footer>🖨️ Агент Secure Print — защита конфиденциальной печати</footer>
|
|
|
+ `;
|
|
|
+ root.innerHTML = html;
|
|
|
+
|
|
|
+ // Обработчики для элементов
|
|
|
+ for (let el of sceneData.elements || []) {
|
|
|
+ const domEl = document.querySelector(`.element[data-element-id="${el.id}"]`);
|
|
|
+ if (domEl && el.onClick) {
|
|
|
+ domEl.addEventListener('click', () => {
|
|
|
+ if (gameActive && !waitingForNext) handleActionResult(el.onClick());
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Обработчики для кнопок
|
|
|
+ for (let act of sceneData.actions || []) {
|
|
|
+ const btn = document.querySelector(`button[data-action-id="${act.id}"]`);
|
|
|
+ if (btn) {
|
|
|
+ btn.addEventListener('click', () => {
|
|
|
+ if (gameActive && !waitingForNext) handleActionResult(act.onClick());
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Обработчики для викторины (варианты ответов)
|
|
|
+ if (sceneData.type === 'quiz') {
|
|
|
+ const answersDivs = document.querySelectorAll('.answer-option');
|
|
|
+ answersDivs.forEach((div, idx) => {
|
|
|
+ div.addEventListener('click', () => {
|
|
|
+ if (gameActive && !waitingForNext) {
|
|
|
+ const answer = sceneData.answers[idx];
|
|
|
+ const feedbackDiv = document.getElementById('quizFeedback');
|
|
|
+ feedbackDiv.style.display = 'block';
|
|
|
+
|
|
|
+ if (answer.correct) {
|
|
|
+ feedbackDiv.innerHTML = `✅ Правильно! ${answer.feedback || ''}`;
|
|
|
+ feedbackDiv.className = 'feedback feedback-correct';
|
|
|
+ score += sceneData.pointsForCorrect || 25;
|
|
|
+ document.querySelector('.score').innerHTML = `🔒 Безопасность: ${score}`;
|
|
|
+
|
|
|
+ // Автоматический переход после правильного ответа
|
|
|
+ waitingForNext = true;
|
|
|
+ setTimeout(() => {
|
|
|
+ if (sceneData.nextSceneOnCorrect) {
|
|
|
+ currentScene = sceneData.nextSceneOnCorrect;
|
|
|
+ waitingForNext = false;
|
|
|
+ render();
|
|
|
+ } else if (sceneData.nextScene) {
|
|
|
+ currentScene = sceneData.nextScene;
|
|
|
+ waitingForNext = false;
|
|
|
+ render();
|
|
|
+ }
|
|
|
+ }, 1500);
|
|
|
+ } else {
|
|
|
+ feedbackDiv.innerHTML = `❌ Неправильно. ${answer.feedback || 'Попробуйте ещё раз!'}`;
|
|
|
+ feedbackDiv.className = 'feedback feedback-wrong';
|
|
|
+ if (sceneData.penaltyOnWrong) {
|
|
|
+ score += sceneData.penaltyOnWrong;
|
|
|
+ document.querySelector('.score').innerHTML = `🔒 Безопасность: ${score}`;
|
|
|
+ }
|
|
|
+ // Блокируем ответы на 1 секунду, но не переходим
|
|
|
+ waitingForNext = true;
|
|
|
+ setTimeout(() => {
|
|
|
+ waitingForNext = false;
|
|
|
+ feedbackDiv.style.display = 'none';
|
|
|
+ }, 1200);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function handleActionResult(result) {
|
|
|
+ if (!result) return;
|
|
|
+
|
|
|
+ if (result.message) {
|
|
|
+ document.getElementById('sceneMessage').innerText = result.message;
|
|
|
+ }
|
|
|
+ if (result.scoreChange) {
|
|
|
+ score += result.scoreChange;
|
|
|
+ }
|
|
|
+ if (result.nextScene) {
|
|
|
+ currentScene = result.nextScene;
|
|
|
+ render();
|
|
|
+ }
|
|
|
+ if (result.gameOver) {
|
|
|
+ gameActive = false;
|
|
|
+ finalMessage = result.gameOver;
|
|
|
+ render();
|
|
|
+ }
|
|
|
+ if (!result.nextScene && !result.gameOver && result.scoreChange !== undefined) {
|
|
|
+ // Обновляем только счёт без перехода
|
|
|
+ render();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function getSceneData(sceneId) {
|
|
|
+ // СЦЕНА 1
|
|
|
+ if (sceneId === 1) {
|
|
|
+ return {
|
|
|
+ title: '📥 Сцена 1: Приёмное отделение',
|
|
|
+ type: 'default',
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ id: 'convent',
|
|
|
+ title: '📨 Жёлтый конверт с грифом "Срочно"',
|
|
|
+ desc: 'Сотрудник Иванов отправил на печать 200 страниц плана “Метеор”. Безопасно ли это?',
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ actions: [
|
|
|
+ {
|
|
|
+ id: 'allow',
|
|
|
+ label: '✅ Разрешить печать',
|
|
|
+ onClick: () => ({ gameOver: '❌ УТЕЧКА! Документы остались в памяти принтера. Вы проиграли.', scoreChange: -100 })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'check',
|
|
|
+ label: '🔍 Запросить уточнение и проверить настройки',
|
|
|
+ onClick: () => ({ message: 'Правильно! Нужно проверить безопасность.', nextScene: 2, scoreChange: 20 })
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ defaultMessage: 'Выберите действие: разрешить печать или запросить уточнение.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // СЦЕНА 2 - ПЕРЕДЕЛАНА В ВИКТОРИНУ С ВАРИАНТАМИ ОТВЕТОВ
|
|
|
+ if (sceneId === 2) {
|
|
|
+ return {
|
|
|
+ title: '⚙️ Сцена 2: Настройка безопасной печати',
|
|
|
+ type: 'quiz',
|
|
|
+ question: 'Какое действие НЕОБХОДИМО выполнить перед печатью конфиденциального документа на сетевом принтере?',
|
|
|
+ answers: [
|
|
|
+ {
|
|
|
+ text: '🔐 Включить функцию Secure Print (печать по PIN-коду)',
|
|
|
+ correct: true,
|
|
|
+ feedback: 'Верно! Secure Print требует ввода PIN-кода на самом принтере, документ не печатается, пока вы не подойдёте к устройству.'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '📤 Отправить документ на печать и забрать через час',
|
|
|
+ correct: false,
|
|
|
+ feedback: 'Ошибка! Документ может быть перехвачен или прочитан посторонними. Забирать распечатку нужно сразу.'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '💾 Сохранить документ в памяти принтера навсегда',
|
|
|
+ correct: false,
|
|
|
+ feedback: 'Неверно! Хранение конфиденциальных данных в памяти принтера — это прямой риск утечки.'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ text: '📧 Отправить документ на печать по электронной почте без шифрования',
|
|
|
+ correct: false,
|
|
|
+ feedback: 'Опасно! Отправка заданий печати по незащищённым каналам может привести к перехвату данных.'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ pointsForCorrect: 30,
|
|
|
+ penaltyOnWrong: -10,
|
|
|
+ nextSceneOnCorrect: 3,
|
|
|
+ defaultMessage: 'Выберите правильный вариант ответа.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // СЦЕНА 3: Конфликт в офисе
|
|
|
+ if (sceneId === 3) {
|
|
|
+ return {
|
|
|
+ title: '🏢 Сцена 3: Конфликт в офисе',
|
|
|
+ type: 'default',
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ id: 'colleague',
|
|
|
+ title: '🧑💼 Взволнованный сотрудник с папкой "Секретно"',
|
|
|
+ desc: '"Мне срочно нужно 5 копий, сисадмин занят! Можно напечатать на соседнем обычном принтере?"',
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ actions: [
|
|
|
+ {
|
|
|
+ id: 'bad',
|
|
|
+ label: '⚠️ Печатать на незащищённом принтере',
|
|
|
+ onClick: () => ({ gameOver: '📡 Перехват данных! Незащищённый принтер скомпрометирован. Игра окончена.', scoreChange: -999 })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'good',
|
|
|
+ label: '⏳ Подождать и использовать безопасный принтер с Secure Print',
|
|
|
+ onClick: () => ({ nextScene: 4, message: 'Правильный выбор! Конфиденциальность сохранена.', scoreChange: 50 })
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ defaultMessage: 'Выберите, как поступить в стрессовой ситуации.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // СЦЕНА 4: Списание старого МФУ
|
|
|
+ if (sceneId === 4) {
|
|
|
+ return {
|
|
|
+ title: '🗑️ Сцена 4: Списание старого МФУ',
|
|
|
+ type: 'default',
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ id: 'oldprinter',
|
|
|
+ title: '🖨️ Старый принтер с табличкой "К списанию"',
|
|
|
+ desc: 'Внутри жёсткий диск с остатками конфиденциальных документов.',
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ actions: [
|
|
|
+ {
|
|
|
+ id: 'destroySW',
|
|
|
+ label: '💾 Запустить сертифицированную программу "Уничтожитель данных"',
|
|
|
+ onClick: () => ({ nextScene: 5, message: 'Диск надёжно стёрт. Молодец!', scoreChange: 40 })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'destroyPhys',
|
|
|
+ label: '🔨 Физически разбить диск и составить акт',
|
|
|
+ onClick: () => ({ nextScene: 5, message: 'Физическое уничтожение зафиксировано. Безопасно.', scoreChange: 40 })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'throw',
|
|
|
+ label: '🗑️ Просто выбросить принтер с диском',
|
|
|
+ onClick: () => ({ gameOver: '💀 Утечка! Злоумышленник извлёк диск из мусора.', scoreChange: -100 })
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ defaultMessage: 'Выберите корректный способ уничтожения данных.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // СЦЕНА 5: Финальный аудит
|
|
|
+ if (sceneId === 5) {
|
|
|
+ return {
|
|
|
+ title: '📋 Сцена 5: Финальный аудит',
|
|
|
+ type: 'default',
|
|
|
+ elements: [
|
|
|
+ {
|
|
|
+ id: 'logbook',
|
|
|
+ title: '📜 Журнал печати за месяц',
|
|
|
+ desc: 'Обнаружены 3 ночные печати по 500 страниц с компьютера Петрова.',
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ actions: [
|
|
|
+ {
|
|
|
+ id: 'warn',
|
|
|
+ label: '📢 Выговор и обязательное обучение',
|
|
|
+ onClick: () => ({ gameOver: '🏆 ПОБЕДА! Политика безопасности усилена. Игра пройдена!', scoreChange: 100 })
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'fire',
|
|
|
+ label: '⚡ Немедленное увольнение без разбора',
|
|
|
+ onClick: () => ({ gameOver: '😐 Нейтральный финал: безопасность в порядке, но моральный климат упал.', scoreChange: 30 })
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ defaultMessage: 'Выберите меру воздействия на нарушителя.'
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ function resetGame() {
|
|
|
+ currentScene = 1;
|
|
|
+ score = 0;
|
|
|
+ gameActive = true;
|
|
|
+ finalMessage = "";
|
|
|
+ waitingForNext = false;
|
|
|
+ render();
|
|
|
+ }
|
|
|
+
|
|
|
+ render();
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+</html>
|