burdyko.html.html 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <!DOCTYPE html>
  2. <html lang="ru">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Агент Secure Print — игра по безопасности печати</title>
  7. <style>
  8. * {
  9. box-sizing: border-box;
  10. user-select: none;
  11. }
  12. body {
  13. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  14. background: linear-gradient(135deg, #1a2a3a, #0d1c2a);
  15. min-height: 100vh;
  16. display: flex;
  17. justify-content: center;
  18. align-items: center;
  19. margin: 0;
  20. padding: 20px;
  21. }
  22. .game-container {
  23. max-width: 800px;
  24. width: 100%;
  25. background: #f5f3ef;
  26. border-radius: 32px;
  27. box-shadow: 0 20px 40px rgba(0,0,0,0.4);
  28. overflow: hidden;
  29. }
  30. .scene {
  31. padding: 30px 25px;
  32. }
  33. .scene-header {
  34. display: flex;
  35. justify-content: space-between;
  36. align-items: baseline;
  37. border-bottom: 2px solid #d0b87a;
  38. margin-bottom: 25px;
  39. flex-wrap: wrap;
  40. }
  41. .scene-title {
  42. font-size: 1.8rem;
  43. font-weight: bold;
  44. color: #2c3e2f;
  45. }
  46. .score {
  47. background: #2c3e2f;
  48. color: #f9e0a8;
  49. padding: 6px 14px;
  50. border-radius: 40px;
  51. font-weight: bold;
  52. }
  53. .scene-content {
  54. min-height: 380px;
  55. }
  56. .question-block {
  57. background: white;
  58. border-radius: 24px;
  59. padding: 20px;
  60. margin-bottom: 20px;
  61. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  62. border: 1px solid #e0cfb0;
  63. }
  64. .question-text {
  65. font-weight: bold;
  66. font-size: 1.2rem;
  67. margin-bottom: 20px;
  68. color: #2c3e2f;
  69. background: #f0eadb;
  70. padding: 15px;
  71. border-radius: 16px;
  72. }
  73. .answers {
  74. display: flex;
  75. flex-direction: column;
  76. gap: 12px;
  77. }
  78. .answer-option {
  79. background: #f9f7f2;
  80. padding: 14px 18px;
  81. border-radius: 16px;
  82. cursor: pointer;
  83. border: 2px solid #e0cfb0;
  84. transition: all 0.1s;
  85. font-size: 1rem;
  86. }
  87. .answer-option:hover {
  88. background: #e8dfcc;
  89. border-color: #b98f4a;
  90. transform: scale(1.01);
  91. }
  92. .element {
  93. background: white;
  94. border-radius: 24px;
  95. padding: 20px;
  96. margin-bottom: 20px;
  97. box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  98. cursor: pointer;
  99. border: 1px solid #e0cfb0;
  100. }
  101. .element:hover {
  102. background: #fffaf2;
  103. border-color: #b98f4a;
  104. }
  105. .element-title {
  106. font-weight: bold;
  107. font-size: 1.3rem;
  108. margin-bottom: 12px;
  109. color: #a45d2e;
  110. display: flex;
  111. align-items: center;
  112. gap: 12px;
  113. }
  114. .actions {
  115. margin-top: 20px;
  116. display: flex;
  117. flex-wrap: wrap;
  118. gap: 15px;
  119. }
  120. button {
  121. background: #2c5f2d;
  122. border: none;
  123. color: white;
  124. font-weight: bold;
  125. padding: 12px 24px;
  126. border-radius: 40px;
  127. font-size: 1rem;
  128. cursor: pointer;
  129. transition: 0.1s;
  130. }
  131. button:hover {
  132. background: #1f4520;
  133. transform: scale(0.97);
  134. }
  135. .message {
  136. background: #e9e3d5;
  137. border-left: 6px solid #b98f4a;
  138. padding: 12px;
  139. margin-top: 20px;
  140. border-radius: 16px;
  141. font-style: italic;
  142. }
  143. .game-over {
  144. background: #8b3c2c;
  145. color: white;
  146. text-align: center;
  147. padding: 30px;
  148. border-radius: 20px;
  149. }
  150. .win {
  151. background: #2c5f2d;
  152. }
  153. footer {
  154. background: #e0cfb0;
  155. padding: 12px;
  156. text-align: center;
  157. font-size: 0.8rem;
  158. color: #2c3e2f;
  159. }
  160. .feedback {
  161. margin-top: 15px;
  162. padding: 10px;
  163. border-radius: 12px;
  164. font-weight: bold;
  165. }
  166. .feedback-correct {
  167. background: #c8e6c9;
  168. color: #1b5e20;
  169. border-left: 4px solid #2e7d32;
  170. }
  171. .feedback-wrong {
  172. background: #ffcdd2;
  173. color: #c62828;
  174. border-left: 4px solid #d32f2f;
  175. }
  176. hr {
  177. margin: 15px 0;
  178. }
  179. </style>
  180. </head>
  181. <body>
  182. <div class="game-container" id="gameRoot"></div>
  183. <script>
  184. // СОСТОЯНИЕ ИГРЫ
  185. let currentScene = 1;
  186. let score = 0;
  187. let gameActive = true;
  188. let finalMessage = "";
  189. let waitingForNext = false;
  190. function render() {
  191. if (!gameActive) {
  192. const root = document.getElementById('gameRoot');
  193. root.innerHTML = `
  194. <div class="scene">
  195. <div class="game-over ${finalMessage.includes('ПОБЕДА') ? 'win' : ''}">
  196. <h2>🔐 ${finalMessage.includes('ПОБЕДА') ? 'ПОБЕДА!' : 'ИГРА ОКОНЧЕНА'}</h2>
  197. <p>${finalMessage}</p>
  198. <p><strong>Итоговый счёт безопасности: ${score}</strong></p>
  199. <button onclick="resetGame()">🔄 Начать заново</button>
  200. </div>
  201. </div>
  202. `;
  203. return;
  204. }
  205. const sceneData = getSceneData(currentScene);
  206. if (!sceneData) return;
  207. const root = document.getElementById('gameRoot');
  208. let contentHtml = '';
  209. if (sceneData.type === 'quiz') {
  210. // Формат с вариантами ответов (как в требовании вариант №1)
  211. contentHtml = `
  212. <div class="question-block">
  213. <div class="question-text">❓ ${sceneData.question}</div>
  214. <div class="answers" id="answersContainer">
  215. ${sceneData.answers.map((ans, idx) => `
  216. <div class="answer-option" data-answer-index="${idx}">
  217. ${ans.text}
  218. </div>
  219. `).join('')}
  220. </div>
  221. <div id="quizFeedback" class="feedback" style="display:none;"></div>
  222. </div>
  223. `;
  224. } else {
  225. // Обычная сцена с элементами
  226. let elementsHtml = '';
  227. for (let el of sceneData.elements || []) {
  228. elementsHtml += `
  229. <div class="element" data-element-id="${el.id}">
  230. <div class="element-title">
  231. <span>📄 ${el.title}</span>
  232. </div>
  233. <div class="element-desc">${el.desc}</div>
  234. </div>
  235. `;
  236. }
  237. contentHtml = elementsHtml;
  238. }
  239. let actionsHtml = '';
  240. for (let act of sceneData.actions || []) {
  241. actionsHtml += `<button data-action-id="${act.id}">${act.label}</button>`;
  242. }
  243. const html = `
  244. <div class="scene">
  245. <div class="scene-header">
  246. <div class="scene-title">${sceneData.title}</div>
  247. <div class="score">🔒 Безопасность: ${score}</div>
  248. </div>
  249. <div class="scene-content">
  250. ${contentHtml}
  251. ${actionsHtml ? `<div class="actions">${actionsHtml}</div>` : ''}
  252. <div class="message" id="sceneMessage">${sceneData.defaultMessage || 'Выберите действие...'}</div>
  253. </div>
  254. </div>
  255. <footer>🖨️ Агент Secure Print — защита конфиденциальной печати</footer>
  256. `;
  257. root.innerHTML = html;
  258. // Обработчики для элементов
  259. for (let el of sceneData.elements || []) {
  260. const domEl = document.querySelector(`.element[data-element-id="${el.id}"]`);
  261. if (domEl && el.onClick) {
  262. domEl.addEventListener('click', () => {
  263. if (gameActive && !waitingForNext) handleActionResult(el.onClick());
  264. });
  265. }
  266. }
  267. // Обработчики для кнопок
  268. for (let act of sceneData.actions || []) {
  269. const btn = document.querySelector(`button[data-action-id="${act.id}"]`);
  270. if (btn) {
  271. btn.addEventListener('click', () => {
  272. if (gameActive && !waitingForNext) handleActionResult(act.onClick());
  273. });
  274. }
  275. }
  276. // Обработчики для викторины (варианты ответов)
  277. if (sceneData.type === 'quiz') {
  278. const answersDivs = document.querySelectorAll('.answer-option');
  279. answersDivs.forEach((div, idx) => {
  280. div.addEventListener('click', () => {
  281. if (gameActive && !waitingForNext) {
  282. const answer = sceneData.answers[idx];
  283. const feedbackDiv = document.getElementById('quizFeedback');
  284. feedbackDiv.style.display = 'block';
  285. if (answer.correct) {
  286. feedbackDiv.innerHTML = `✅ Правильно! ${answer.feedback || ''}`;
  287. feedbackDiv.className = 'feedback feedback-correct';
  288. score += sceneData.pointsForCorrect || 25;
  289. document.querySelector('.score').innerHTML = `🔒 Безопасность: ${score}`;
  290. // Автоматический переход после правильного ответа
  291. waitingForNext = true;
  292. setTimeout(() => {
  293. if (sceneData.nextSceneOnCorrect) {
  294. currentScene = sceneData.nextSceneOnCorrect;
  295. waitingForNext = false;
  296. render();
  297. } else if (sceneData.nextScene) {
  298. currentScene = sceneData.nextScene;
  299. waitingForNext = false;
  300. render();
  301. }
  302. }, 1500);
  303. } else {
  304. feedbackDiv.innerHTML = `❌ Неправильно. ${answer.feedback || 'Попробуйте ещё раз!'}`;
  305. feedbackDiv.className = 'feedback feedback-wrong';
  306. if (sceneData.penaltyOnWrong) {
  307. score += sceneData.penaltyOnWrong;
  308. document.querySelector('.score').innerHTML = `🔒 Безопасность: ${score}`;
  309. }
  310. // Блокируем ответы на 1 секунду, но не переходим
  311. waitingForNext = true;
  312. setTimeout(() => {
  313. waitingForNext = false;
  314. feedbackDiv.style.display = 'none';
  315. }, 1200);
  316. }
  317. }
  318. });
  319. });
  320. }
  321. }
  322. function handleActionResult(result) {
  323. if (!result) return;
  324. if (result.message) {
  325. document.getElementById('sceneMessage').innerText = result.message;
  326. }
  327. if (result.scoreChange) {
  328. score += result.scoreChange;
  329. }
  330. if (result.nextScene) {
  331. currentScene = result.nextScene;
  332. render();
  333. }
  334. if (result.gameOver) {
  335. gameActive = false;
  336. finalMessage = result.gameOver;
  337. render();
  338. }
  339. if (!result.nextScene && !result.gameOver && result.scoreChange !== undefined) {
  340. // Обновляем только счёт без перехода
  341. render();
  342. }
  343. }
  344. function getSceneData(sceneId) {
  345. // СЦЕНА 1
  346. if (sceneId === 1) {
  347. return {
  348. title: '📥 Сцена 1: Приёмное отделение',
  349. type: 'default',
  350. elements: [
  351. {
  352. id: 'convent',
  353. title: '📨 Жёлтый конверт с грифом "Срочно"',
  354. desc: 'Сотрудник Иванов отправил на печать 200 страниц плана “Метеор”. Безопасно ли это?',
  355. }
  356. ],
  357. actions: [
  358. {
  359. id: 'allow',
  360. label: '✅ Разрешить печать',
  361. onClick: () => ({ gameOver: '❌ УТЕЧКА! Документы остались в памяти принтера. Вы проиграли.', scoreChange: -100 })
  362. },
  363. {
  364. id: 'check',
  365. label: '🔍 Запросить уточнение и проверить настройки',
  366. onClick: () => ({ message: 'Правильно! Нужно проверить безопасность.', nextScene: 2, scoreChange: 20 })
  367. }
  368. ],
  369. defaultMessage: 'Выберите действие: разрешить печать или запросить уточнение.'
  370. };
  371. }
  372. // СЦЕНА 2 - ПЕРЕДЕЛАНА В ВИКТОРИНУ С ВАРИАНТАМИ ОТВЕТОВ
  373. if (sceneId === 2) {
  374. return {
  375. title: '⚙️ Сцена 2: Настройка безопасной печати',
  376. type: 'quiz',
  377. question: 'Какое действие НЕОБХОДИМО выполнить перед печатью конфиденциального документа на сетевом принтере?',
  378. answers: [
  379. {
  380. text: '🔐 Включить функцию Secure Print (печать по PIN-коду)',
  381. correct: true,
  382. feedback: 'Верно! Secure Print требует ввода PIN-кода на самом принтере, документ не печатается, пока вы не подойдёте к устройству.'
  383. },
  384. {
  385. text: '📤 Отправить документ на печать и забрать через час',
  386. correct: false,
  387. feedback: 'Ошибка! Документ может быть перехвачен или прочитан посторонними. Забирать распечатку нужно сразу.'
  388. },
  389. {
  390. text: '💾 Сохранить документ в памяти принтера навсегда',
  391. correct: false,
  392. feedback: 'Неверно! Хранение конфиденциальных данных в памяти принтера — это прямой риск утечки.'
  393. },
  394. {
  395. text: '📧 Отправить документ на печать по электронной почте без шифрования',
  396. correct: false,
  397. feedback: 'Опасно! Отправка заданий печати по незащищённым каналам может привести к перехвату данных.'
  398. }
  399. ],
  400. pointsForCorrect: 30,
  401. penaltyOnWrong: -10,
  402. nextSceneOnCorrect: 3,
  403. defaultMessage: 'Выберите правильный вариант ответа.'
  404. };
  405. }
  406. // СЦЕНА 3: Конфликт в офисе
  407. if (sceneId === 3) {
  408. return {
  409. title: '🏢 Сцена 3: Конфликт в офисе',
  410. type: 'default',
  411. elements: [
  412. {
  413. id: 'colleague',
  414. title: '🧑‍💼 Взволнованный сотрудник с папкой "Секретно"',
  415. desc: '"Мне срочно нужно 5 копий, сисадмин занят! Можно напечатать на соседнем обычном принтере?"',
  416. }
  417. ],
  418. actions: [
  419. {
  420. id: 'bad',
  421. label: '⚠️ Печатать на незащищённом принтере',
  422. onClick: () => ({ gameOver: '📡 Перехват данных! Незащищённый принтер скомпрометирован. Игра окончена.', scoreChange: -999 })
  423. },
  424. {
  425. id: 'good',
  426. label: '⏳ Подождать и использовать безопасный принтер с Secure Print',
  427. onClick: () => ({ nextScene: 4, message: 'Правильный выбор! Конфиденциальность сохранена.', scoreChange: 50 })
  428. }
  429. ],
  430. defaultMessage: 'Выберите, как поступить в стрессовой ситуации.'
  431. };
  432. }
  433. // СЦЕНА 4: Списание старого МФУ
  434. if (sceneId === 4) {
  435. return {
  436. title: '🗑️ Сцена 4: Списание старого МФУ',
  437. type: 'default',
  438. elements: [
  439. {
  440. id: 'oldprinter',
  441. title: '🖨️ Старый принтер с табличкой "К списанию"',
  442. desc: 'Внутри жёсткий диск с остатками конфиденциальных документов.',
  443. }
  444. ],
  445. actions: [
  446. {
  447. id: 'destroySW',
  448. label: '💾 Запустить сертифицированную программу "Уничтожитель данных"',
  449. onClick: () => ({ nextScene: 5, message: 'Диск надёжно стёрт. Молодец!', scoreChange: 40 })
  450. },
  451. {
  452. id: 'destroyPhys',
  453. label: '🔨 Физически разбить диск и составить акт',
  454. onClick: () => ({ nextScene: 5, message: 'Физическое уничтожение зафиксировано. Безопасно.', scoreChange: 40 })
  455. },
  456. {
  457. id: 'throw',
  458. label: '🗑️ Просто выбросить принтер с диском',
  459. onClick: () => ({ gameOver: '💀 Утечка! Злоумышленник извлёк диск из мусора.', scoreChange: -100 })
  460. }
  461. ],
  462. defaultMessage: 'Выберите корректный способ уничтожения данных.'
  463. };
  464. }
  465. // СЦЕНА 5: Финальный аудит
  466. if (sceneId === 5) {
  467. return {
  468. title: '📋 Сцена 5: Финальный аудит',
  469. type: 'default',
  470. elements: [
  471. {
  472. id: 'logbook',
  473. title: '📜 Журнал печати за месяц',
  474. desc: 'Обнаружены 3 ночные печати по 500 страниц с компьютера Петрова.',
  475. }
  476. ],
  477. actions: [
  478. {
  479. id: 'warn',
  480. label: '📢 Выговор и обязательное обучение',
  481. onClick: () => ({ gameOver: '🏆 ПОБЕДА! Политика безопасности усилена. Игра пройдена!', scoreChange: 100 })
  482. },
  483. {
  484. id: 'fire',
  485. label: '⚡ Немедленное увольнение без разбора',
  486. onClick: () => ({ gameOver: '😐 Нейтральный финал: безопасность в порядке, но моральный климат упал.', scoreChange: 30 })
  487. }
  488. ],
  489. defaultMessage: 'Выберите меру воздействия на нарушителя.'
  490. };
  491. }
  492. return null;
  493. }
  494. function resetGame() {
  495. currentScene = 1;
  496. score = 0;
  497. gameActive = true;
  498. finalMessage = "";
  499. waitingForNext = false;
  500. render();
  501. }
  502. render();
  503. </script>
  504. </body>
  505. </html>