ypv 9 часов назад
Родитель
Сommit
c6ee4c9041
3 измененных файлов с 1440 добавлено и 1 удалено
  1. 5 1
      tzi.md
  2. 1189 0
      ПМ.03.01/4.2/001/index.html
  3. 246 0
      ПМ.03.01/4.2/001/specification.md

+ 5 - 1
tzi.md

@@ -224,7 +224,7 @@
 
 | № | Название модели (ссылка на Gogs) | Автор (Студент) | Демо (Steelfeet) | Тип |
 |:--|:---------------------------------|:----------------|:-----------------|:---:|
-| 1 | [Электромагнитный канал утечки информации, ч.2, гальваническя наводка](ПМ.03.01/3.1.400/001/) | ЯПВ | [Демо](https://steelfeet.ru/zaslon/ПМ.03.01/3.1.400/001/) | 🟢 |
+| 1 | [Электромагнитный канал утечки информации, ч.2, гальваническая наводка](ПМ.03.01/3.1.400/001/) | ЯПВ | [Демо](https://steelfeet.ru/zaslon/ПМ.03.01/3.1.400/001/) | 🟢 |
 
 **3.1.600 Номенклатура и характеристика аппаратуры для измерения параметров ПЭМИН, фоновых шумов и физических полей.**
 
@@ -349,8 +349,12 @@
 *   П4.1.100 Защита от утечки по акустическому каналу.
 
 ### Тема 4.2. Системы защиты от утечки информации по проводному каналу
+| № | Название модели (ссылка на Gogs) | Автор (Студент) | Демо (Steelfeet) | Тип |
+|:--|:---------------------------------|:----------------|:-----------------|:---:|
+| 001| ["Линия под контролем"](ПМ.03.01/4.2/001/) | ЯПВ | [Демо](https://steelfeet.ru/zaslon/ПМ.03.01/4.2/001/) | 🟢 |
 
 **4.2.100 Принцип работы микрофона и телефона.**
+                             |                 |                  |     |
 
 | № | Название модели (ссылка на Gogs) | Автор (Студент) | Демо (Steelfeet) | Тип |
 |:--|:---------------------------------|:----------------|:-----------------|:---:|

+ 1189 - 0
ПМ.03.01/4.2/001/index.html

@@ -0,0 +1,1189 @@
+<!DOCTYPE html>
+<html lang="ru">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Линия под контролем — защита от утечки по проводному каналу</title>
+<style>
+@import url('https://fonts.googleapis.com/css2?family=Oswald:wght@500;700&family=PT+Serif:wght@400;700&family=Roboto+Mono:wght@400;700&display=swap');
+* { box-sizing: border-box; }
+html, body {
+  margin: 0; padding: 0;
+  background: #efe8d8; color: #1a2744;
+  font-family: 'PT Serif', Georgia, serif;
+  font-size: 17px; line-height: 1.55;
+  -webkit-font-smoothing: antialiased;
+}
+body {
+  min-height: 100vh; display: flex; flex-direction: column;
+  background-image:
+    linear-gradient(rgba(26,39,68,0.045) 1px, transparent 1px),
+    linear-gradient(90deg, rgba(26,39,68,0.045) 1px, transparent 1px);
+  background-size: 28px 28px;
+}
+header {
+  position: sticky; top: 0; z-index: 10;
+  background: #efe8d8; border-bottom: 2px solid #1a2744;
+  padding: 8px 16px 10px;
+}
+.progress { height: 4px; background: rgba(26,39,68,0.15); position: relative; overflow: hidden; }
+.progress-fill { position: absolute; inset: 0; width: 0%; background: #b02a1e; transition: width 0.3s ease; }
+.header-row {
+  display: flex; justify-content: space-between; align-items: center;
+  margin-top: 8px;
+  font-family: 'Oswald', 'Arial Narrow', sans-serif;
+  font-size: 13px; letter-spacing: 1px; text-transform: uppercase;
+  color: #4a5875;
+}
+.header-row .brand { color: #b02a1e; font-weight: 700; }
+.header-scene { color: #4a5875; }
+.header-scene #sceneNum { color: #b02a1e; font-weight: 700; }
+main {
+  flex: 1; max-width: 900px; width: 100%;
+  margin: 0 auto; padding: 24px 16px 90px;
+}
+h1, h2, h3 { font-family: 'Oswald', 'Arial Narrow', sans-serif; font-weight: 700; letter-spacing: 0.5px; line-height: 1.15; color: #1a2744; }
+h1 { font-size: 30px; margin: 0 0 12px; }
+h2 { font-size: 22px; margin: 24px 0 12px; text-transform: uppercase; letter-spacing: 1.5px; border-bottom: 2px solid #1a2744; padding-bottom: 6px; }
+h3 { font-size: 17px; margin: 16px 0 8px; text-transform: uppercase; letter-spacing: 1px; }
+p { margin: 0 0 12px; }
+.goal-box { border: 2px solid #1a2744; background: #f6f0df; padding: 16px 18px; margin-bottom: 16px; position: relative; }
+.goal-box::before {
+  content: 'ЦЕЛЬ'; position: absolute; top: -10px; left: 14px;
+  background: #b02a1e; color: #efe8d8; padding: 2px 10px;
+  font-family: 'Oswald', sans-serif; font-size: 11px; letter-spacing: 2px;
+}
+.defs-box { border: 2px dashed #b02a1e; background: #fff8e8; padding: 14px 18px; margin: 16px 0; }
+.defs-box::before {
+  content: 'ЗАПИШИ В ТЕТРАДЬ'; display: block;
+  font-family: 'Oswald', sans-serif; font-size: 12px; letter-spacing: 2px;
+  color: #b02a1e; margin-bottom: 10px;
+  border-bottom: 1px solid #b02a1e; padding-bottom: 6px;
+}
+.defs-box dt { font-weight: 700; font-family: 'Oswald', sans-serif; font-size: 14px; margin-top: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
+.defs-box dd { margin: 2px 0 0; font-size: 15px; }
+.controls { background: #f6f0df; border: 1.5px solid #1a2744; padding: 12px 14px; margin: 12px 0; }
+.ctrl-row { display: flex; align-items: center; gap: 10px; margin: 8px 0; font-family: 'Roboto Mono', 'Courier New', monospace; font-size: 13px; }
+.ctrl-row label { flex: 0 0 auto; }
+.ctrl-row input[type=range] { flex: 1; min-width: 80px; accent-color: #b02a1e; }
+.ctrl-row .val { flex: 0 0 85px; text-align: right; color: #b02a1e; font-weight: 700; }
+.seg { display: flex; gap: 0; border: 1.5px solid #1a2744; margin: 8px 0; }
+.seg button {
+  flex: 1; padding: 8px 4px; background: #f6f0df;
+  border: none; border-right: 1px solid #1a2744;
+  font-family: 'Oswald', sans-serif; font-size: 12px; letter-spacing: 0.3px;
+  text-transform: uppercase; color: #1a2744; cursor: pointer;
+  transition: background 0.15s;
+}
+.seg button:last-child { border-right: none; }
+.seg button.active { background: #1a2744; color: #efe8d8; }
+.seg button:hover:not(.active) { background: #e5dcc7; }
+canvas.viz { display: block; width: 100%; height: 240px; background: #fbf7eb; border: 1.5px solid #1a2744; margin: 8px 0; touch-action: none; }
+canvas.viz.tall { height: 340px; }
+.readout { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 10px 0; }
+.readout .item { background: #f6f0df; border: 1px solid #1a2744; padding: 6px 8px; font-family: 'Roboto Mono', monospace; font-size: 12px; }
+.readout .item .lbl { color: #4a5875; text-transform: uppercase; letter-spacing: 0.5px; font-size: 10px; }
+.readout .item .val { font-size: 16px; font-weight: 700; color: #1a2744; word-break: break-word; }
+.readout .item .val.warn { color: #b02a1e; }
+.readout .item .val.ok { color: #2d5a3d; }
+.feedback { border-left: 4px solid #b02a1e; background: #fbf3e0; padding: 10px 14px; margin: 12px 0; font-size: 15px; }
+.feedback strong { color: #b02a1e; }
+.triad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 16px 0; }
+.triad .card { border: 1.5px solid #1a2744; background: #f6f0df; padding: 12px; cursor: pointer; transition: all 0.15s; }
+.triad .card:hover { background: #ece2c8; }
+.triad .card.active { background: #1a2744; color: #efe8d8; }
+.triad .card h4 { margin: 0 0 6px; font-family: 'Oswald', sans-serif; font-size: 14px; letter-spacing: 1px; text-transform: uppercase; }
+.triad .card p { margin: 0; font-size: 13px; line-height: 1.4; }
+.triad .card .icon { font-size: 24px; display: block; margin-bottom: 4px; }
+.btn { font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: 1.5px; text-transform: uppercase; padding: 10px 20px; background: #1a2744; color: #efe8d8; border: none; cursor: pointer; transition: background 0.15s; }
+.btn:hover { background: #2d3a5a; }
+.btn:disabled { opacity: 0.4; cursor: not-allowed; }
+.btn.ghost { background: transparent; color: #1a2744; border: 1.5px solid #1a2744; }
+.btn.ghost:hover { background: #f6f0df; }
+footer {
+  position: fixed; bottom: 0; left: 0; right: 0;
+  background: #efe8d8; border-top: 2px solid #1a2744;
+  padding: 10px 16px;
+  display: flex; justify-content: space-between; align-items: center;
+  font-family: 'Oswald', sans-serif; font-size: 13px; letter-spacing: 1px;
+}
+footer .counter { color: #4a5875; }
+.test-item { border: 1.5px solid #1a2744; background: #f6f0df; padding: 14px; margin: 12px 0; }
+.test-item .q { font-weight: 700; margin-bottom: 10px; }
+.test-item .opts { display: flex; flex-direction: column; gap: 6px; }
+.test-item .opt { padding: 8px 12px; border: 1px solid #4a5875; background: #fbf7eb; cursor: pointer; font-size: 14px; transition: all 0.15s; }
+.test-item .opt:hover { background: #ece2c8; }
+.test-item .opt.correct { background: #d8e8d0; border-color: #2d5a3d; }
+.test-item .opt.wrong { background: #f0d0c8; border-color: #b02a1e; }
+.test-item .opt.disabled { cursor: default; pointer-events: none; }
+.test-item .explain { display: none; margin-top: 10px; padding: 10px; background: #fff8e8; border-left: 3px solid #2d5a3d; font-size: 14px; }
+.test-item .explain.visible { display: block; }
+.checklist { border: 2px solid #2d5a3d; background: #f0f6ec; padding: 16px 18px; margin: 16px 0; }
+.checklist h3 { color: #2d5a3d; border: none; margin-top: 0; }
+.checklist ul { margin: 0; padding-left: 20px; }
+.checklist li { margin: 4px 0; }
+@media (max-width: 600px) {
+  h1 { font-size: 22px; } h2 { font-size: 17px; }
+  .readout { grid-template-columns: 1fr 1fr; }
+  .triad { grid-template-columns: 1fr; }
+  canvas.viz { height: 200px; }
+  canvas.viz.tall { height: 300px; }
+  main { padding: 16px 10px 90px; }
+  .ctrl-row .val { flex: 0 0 70px; font-size: 12px; }
+}
+</style>
+</head>
+<body>
+
+<header>
+  <div class="progress"><div class="progress-fill" id="progressFill"></div></div>
+  <div class="header-row">
+    <span class="brand">Заслон · Тема 4.2</span>
+    <span class="header-scene"><span id="sceneNum">Сцена 1 из 9</span> · <span id="sceneTitle">Цель</span></span>
+  </div>
+</header>
+
+<main id="main"></main>
+
+<footer>
+  <button class="btn ghost" id="prevBtn">← Назад</button>
+  <span class="counter" id="counter">1 / 9</span>
+  <button class="btn" id="nextBtn">Далее →</button>
+</footer>
+
+<script>
+'use strict';
+
+/* ---------- общие утилиты ---------- */
+let currentRender = null;
+function stopAnim() { /* заглушка под будущие анимации */ }
+
+function setupCanvas(canvas) {
+  const dpr = window.devicePixelRatio || 1;
+  let rect = canvas.getBoundingClientRect();
+  if (rect.width < 10) {
+    const pw = canvas.parentElement ? canvas.parentElement.getBoundingClientRect().width : 0;
+    rect = { width: pw || 600, height: canvas.offsetHeight || 240 };
+  }
+  canvas.width  = Math.max(1, Math.floor(rect.width))  * dpr;
+  canvas.height = Math.max(1, Math.floor(rect.height)) * dpr;
+  const ctx = canvas.getContext('2d');
+  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+  return { ctx, w: rect.width, h: rect.height };
+}
+
+function setTitle(t) { document.getElementById('sceneTitle').textContent = t; }
+
+/* ---------- Сцена 1. Цель ---------- */
+function scene0(container) {
+  setTitle('Цель');
+  container.innerHTML = `
+    <h1>Линия под контролем</h1>
+    <p style="font-size:16px;color:#4a5875;margin-bottom:20px;">Тема 4.2. Системы защиты от утечки информации по проводному каналу</p>
+    <div class="goal-box">
+      <p style="margin-bottom:8px;"><strong>После прохождения вы сможете:</strong></p>
+      <ul style="margin:0;padding-left:20px;">
+        <li>Для каждой из трёх угроз ИБ — конфиденциальности, целостности, доступности — назвать способ атаки на проводную линию.</li>
+        <li>Для каждой угрозы назвать средство защиты, которое её закрывает.</li>
+        <li>Объяснить, почему один и тот же прибор может обслуживать две разные угрозы.</li>
+      </ul>
+    </div>
+    <p>Проводной канал — телефонная линия, радиовещательная сеть, силовая проводка, линии сигнализации — остаётся важным, даже когда вокруг цифра. Именно поэтому его защита — отдельная тема, а не приложение к акустике или электромагнетизму.</p>
+    <p>Разберём три угрозы по очереди, потом сведём их в одну таблицу. В конце — тест на понимание.</p>
+    <div class="controls" style="margin-top:24px;">
+      <div style="font-family:'Oswald',sans-serif;font-size:12px;letter-spacing:1px;color:#4a5875;text-transform:uppercase;margin-bottom:8px;">Что впереди</div>
+      <div style="font-family:'Roboto Mono',monospace;font-size:13px;line-height:1.8;">
+        1. Зачем линии, когда есть цифра<br>
+        2. Определения для тетради<br>
+        3. Угроза конфиденциальности: подключение<br>
+        4. Угроза целостности: подмена сигнала<br>
+        5. Угроза доступности: обрыв и подавление<br>
+        6. Применение: средства защиты по триаде<br>
+        7. Проверка понимания<br>
+        8. Итог
+      </div>
+    </div>`;
+}
+
+/* ---------- Сцена 2. Актуализация ---------- */
+function scene1(container) {
+  setTitle('Зачем линии, когда есть цифра');
+  container.innerHTML = `
+    <h2>Зачем линии, когда есть цифра</h2>
+    <p>Проводное радиовещание и линии оповещения сохраняют и развивают, потому что они <strong>энергонезависимы</strong>: питаются от самой линии, а не от розетки, и работают, когда легла сотовая сеть и отключили свет. Но та же энергонезависимость означает, что провод физически доступен.</p>
+
+    <div class="controls">
+      <div class="ctrl-row">
+        <label>Расстояние до щитка:</label>
+        <input type="range" id="s1-dist" min="0" max="1000" step="10" value="0">
+        <span class="val" id="s1-dist-val">0 м</span>
+      </div>
+    </div>
+
+    <canvas class="viz" id="s1-canvas" style="height:220px;"></canvas>
+
+    <div class="feedback" id="s1-feedback">Проведите ползунок вправо — на линии появятся точки возможного доступа.</div>
+
+    <div class="triad">
+      <div class="card"><span class="icon">👁</span><h4>Конфиденциальность</h4><p>Сигнал прочитан тем, кому не предназначен.</p></div>
+      <div class="card"><span class="icon">✎</span><h4>Целостность</h4><p>Сигнал изменён или подменён по пути.</p></div>
+      <div class="card"><span class="icon">⛓</span><h4>Доступность</h4><p>Сигнал не доходит до получателя.</p></div>
+    </div>
+    <p>Линия, которую берегут именно за надёжность, становится мишенью по всем трём осям. Дальше — по одной.</p>`;
+
+  const canvas = container.querySelector('#s1-canvas');
+  const slider = container.querySelector('#s1-dist');
+  const lbl = container.querySelector('#s1-dist-val');
+  const fb = container.querySelector('#s1-feedback');
+
+  function render() {
+    const dist = parseInt(slider.value);
+    lbl.textContent = dist + ' м';
+    drawScene1(canvas, dist);
+    if (dist === 0) fb.innerHTML = 'Проведите ползунок вправо — появятся точки доступа.';
+    else if (dist < 300) fb.innerHTML = 'Открыт щиток в подъезде. Злоумышленник уже может подключиться.';
+    else if (dist < 600) fb.innerHTML = '<strong>Подвал.</strong> Ещё одна точка доступа. Контроль только квартиры не спасает — нужен контроль всей длины.';
+    else if (dist < 900) fb.innerHTML = '<strong>Чердак.</strong> Чем длиннее линия, тем больше физических точек.';
+    else fb.innerHTML = '<strong>Кабельная трасса.</strong> Вся линия открыта. Защита начинается с контроля всей длины, а не только квартиры.';
+  }
+  slider.addEventListener('input', render);
+  currentRender = render;
+  render();
+  requestAnimationFrame(render);
+}
+
+function drawScene1(canvas, dist) {
+  const { ctx, w, h } = setupCanvas(canvas);
+  ctx.clearRect(0, 0, w, h);
+
+  const pad = 30, lineY = h * 0.55;
+  const lineLeft = pad, lineRight = w - pad;
+  const lineLen = lineRight - lineLeft;
+  const mToPx = (m) => lineLeft + (m / 1000) * lineLen;
+
+  // дома
+  [0, 200, 400, 700, 950].forEach(m => {
+    const x = mToPx(m);
+    ctx.fillStyle = '#1a2744';
+    ctx.fillRect(x - 12, lineY - 26, 24, 20);
+    ctx.beginPath();
+    ctx.moveTo(x - 14, lineY - 26); ctx.lineTo(x, lineY - 36); ctx.lineTo(x + 14, lineY - 26);
+    ctx.closePath(); ctx.fill();
+    ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 1.5;
+    ctx.beginPath(); ctx.moveTo(x, lineY - 36); ctx.lineTo(x, lineY - 44); ctx.stroke();
+  });
+
+  // линия
+  ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+  ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+
+  // точки доступа
+  [{ m: 100, name: 'щиток' }, { m: 300, name: 'подвал' }, { m: 600, name: 'чердак' }, { m: 900, name: 'трасса' }].forEach(ap => {
+    if (dist >= ap.m - 50) {
+      const x = mToPx(ap.m);
+      ctx.save(); ctx.translate(x, lineY); ctx.rotate(Math.PI / 4);
+      ctx.fillStyle = '#b02a1e'; ctx.fillRect(-8, -8, 16, 16); ctx.restore();
+      ctx.fillStyle = '#b02a1e'; ctx.font = 'bold 11px "Roboto Mono", monospace';
+      ctx.textAlign = 'center'; ctx.fillText(ap.name, x, lineY + 32);
+    }
+  });
+
+  ctx.fillStyle = '#4a5875'; ctx.font = '11px "Roboto Mono", monospace';
+  ctx.textAlign = 'left'; ctx.fillText('0 м', lineLeft, lineY + 55);
+  ctx.textAlign = 'right'; ctx.fillText('1000 м', lineRight, lineY + 55);
+}
+
+/* ---------- Сцена 3. Определения ---------- */
+function scene2(container) {
+  setTitle('Определения для тетради');
+  container.innerHTML = `
+    <h2>Определения для тетради</h2>
+    <p>Коротко, чтобы записать и дальше опираться.</p>
+    <div class="defs-box">
+      <dl>
+        <dt>Проводной канал утечки</dt>
+        <dd>Канал, в котором информативный сигнал распространяется по проводной линии: телефонной, радиовещательной, силовой, сигнализационной.</dd>
+
+        <dt>Конфиденциальность</dt>
+        <dd>Сигнал прочитан тем, кому не предназначен.</dd>
+
+        <dt>Целостность</dt>
+        <dd>Сигнал изменён или подменён по пути.</dd>
+
+        <dt>Доступность</dt>
+        <dd>Сигнал не доходит до получателя.</dd>
+
+        <dt>Параллельное подключение</dt>
+        <dd>Устройство съёма подключается к линии как дополнительная нагрузка. Меняет ток и импеданс линии.</dd>
+
+        <dt>Последовательное подключение</dt>
+        <dd>Устройство съёма разрывает линию и включается в разрыв. Меняет сопротивление участка.</dd>
+
+        <dt>Рефлектометрия (TDR)</dt>
+        <dd>Метод обнаружения неоднородностей в линии по отражённому импульсу. Позволяет найти место подключения или обрыва.</dd>
+      </dl>
+    </div>
+    <p style="color:#4a5875;font-size:14px;">Дальше — по одной угрозе. Сначала конфиденциальность: подключение к линии.</p>`;
+}
+
+/* ---------- хелперы отрисовки схемы ---------- */
+function drawLabel(ctx, text, x, y, align, size, color) {
+  align = align || 'center'; size = size || 13; color = color || '#1a2744';
+  ctx.font = 'bold ' + size + 'px "Roboto Mono", monospace';
+  const w = ctx.measureText(text).width;
+  let bx = x;
+  if (align === 'center') bx = x - w / 2;
+  else if (align === 'right') bx = x - w;
+  ctx.fillStyle = 'rgba(251,247,235,0.92)';
+  ctx.fillRect(bx - 4, y - size + 1, w + 8, size + 5);
+  ctx.fillStyle = color;
+  ctx.textAlign = 'left';
+  ctx.fillText(text, bx, y);
+}
+
+function drawDevice(ctx, cx, cy, w, h, label, color) {
+  ctx.fillStyle = color;
+  ctx.fillRect(cx - w / 2, cy - h / 2, w, h);
+  ctx.strokeStyle = '#1a2744';
+  ctx.lineWidth = 2;
+  ctx.strokeRect(cx - w / 2, cy - h / 2, w, h);
+  ctx.fillStyle = '#efe8d8';
+  ctx.font = 'bold 12px "Oswald", sans-serif';
+  ctx.textAlign = 'center';
+  ctx.textBaseline = 'middle';
+  ctx.fillText(label, cx, cy);
+  ctx.textBaseline = 'alphabetic';
+}
+
+function drawSourceBox(ctx, x, y, w, h, label) {
+  ctx.fillStyle = '#1a2744';
+  ctx.fillRect(x, y, w, h);
+  ctx.fillStyle = '#efe8d8';
+  ctx.font = 'bold 12px "Oswald", sans-serif';
+  ctx.textAlign = 'center';
+  ctx.textBaseline = 'middle';
+  ctx.fillText(label, x + w / 2, y + h / 2);
+  ctx.textBaseline = 'alphabetic';
+}
+
+/* ---------- Сцена 4. Конфиденциальность ---------- */
+function scene3(container) {
+  setTitle('Угроза конфиденциальности');
+  container.innerHTML = `
+    <h2>Угроза конфиденциальности: подключение к линии</h2>
+    <p>Злоумышленник подключается к линии, чтобы читать сигнал. Есть два способа: <strong>параллельно</strong> (дополнительная нагрузка) и <strong>последовательно</strong> (разрыв с вставкой). Каждый меняет параметры линии по-своему.</p>
+
+    <div class="controls">
+      <div class="seg" id="s3-type">
+        <button data-val="none" class="active">Нет</button>
+        <button data-val="parallel">Параллельное</button>
+        <button data-val="serial">Последовательное</button>
+      </div>
+      <div class="ctrl-row">
+        <label>R злоумышленника:</label>
+        <input type="range" id="s3-R" min="1" max="1000" step="1" value="1000">
+        <span class="val" id="s3-R-val">1000 Ом</span>
+      </div>
+      <div class="ctrl-row">
+        <label>Удалённость:</label>
+        <input type="range" id="s3-dist" min="50" max="950" step="10" value="300">
+        <span class="val" id="s3-dist-val">300 м</span>
+      </div>
+    </div>
+
+    <canvas class="viz tall" id="s3-canvas"></canvas>
+
+    <div class="readout">
+      <div class="item"><div class="lbl">Ток в линии</div><div class="val" id="s3-I">—</div></div>
+      <div class="item"><div class="lbl">Импеданс</div><div class="val" id="s3-Z">—</div></div>
+      <div class="item"><div class="lbl">Отражение Γ</div><div class="val" id="s3-G">—</div></div>
+    </div>
+
+    <div class="feedback" id="s3-feedback">Выберите тип подключения и двигайте ползунки.</div>`;
+
+  const st = { type: 'none', R: 1000, dist: 300 };
+  const canvas = container.querySelector('#s3-canvas');
+
+  function render() {
+    st.R = parseInt(container.querySelector('#s3-R').value);
+    st.dist = parseInt(container.querySelector('#s3-dist').value);
+    container.querySelector('#s3-R-val').textContent = st.R + ' Ом';
+    container.querySelector('#s3-dist-val').textContent = st.dist + ' м';
+
+    const Z0 = 100, U = 60;
+    let Z, I, Gamma;
+    const I0 = U / (2 * Z0);
+
+    if (st.type === 'none') {
+      Z = 2 * Z0; I = I0; Gamma = 0;
+    } else if (st.type === 'parallel') {
+      const Zt = (st.R * Z0) / (st.R + Z0);
+      Z = Z0 + Zt; I = U / Z;
+      Gamma = (Zt - Z0) / (Zt + Z0);
+    } else {
+      const Zt = Z0 + st.R;
+      Z = Z0 + Zt; I = U / Z;
+      Gamma = (Zt - Z0) / (Zt + Z0);
+    }
+    const dI = ((I - I0) / I0) * 100;
+
+    const iEl = container.querySelector('#s3-I');
+    iEl.textContent = I.toFixed(2) + ' А';
+    iEl.className = 'val' + (Math.abs(dI) > 5 ? ' warn' : '');
+    container.querySelector('#s3-Z').textContent = Z.toFixed(1) + ' Ом';
+    container.querySelector('#s3-G').textContent = Gamma.toFixed(3);
+
+    drawScene3(canvas, st, Gamma);
+
+    const fb = container.querySelector('#s3-feedback');
+    if (st.type === 'none') {
+      fb.innerHTML = 'Линия чистая. Параметры в норме.';
+    } else if (st.type === 'parallel') {
+      if (st.R < 5) {
+        fb.innerHTML = '<strong>Короткое замыкание.</strong> Сопротивление почти нулевое — это уже не скрытый съём, а авария. Система контроля должна сработать и на это.';
+      } else if (Math.abs(dI) < 5) {
+        fb.innerHTML = '<strong>Подключение маскируется.</strong> Сопротивление велико, ток изменился меньше чем на 5% — на глаз не заметить. Нужен точный контроль импеданса или рефлектометрия.';
+      } else {
+        fb.innerHTML = `<strong>Параллельное подключение.</strong> Линия шунтируется: сопротивление падает, ток растёт на ${Math.abs(dI).toFixed(1)}%. Если сравнивать с эталоном — заметно.`;
+      }
+    } else {
+      if (Math.abs(dI) < 5) {
+        fb.innerHTML = '<strong>Вставка почти невидима по току.</strong> Маленькое сопротивление в разрыве почти не меняет ток. Но рефлектометр видит неоднородность.';
+      } else {
+        fb.innerHTML = `<strong>Последовательное подключение.</strong> В линии появился разрыв с сопротивлением ${st.R} Ом. Ток упал на ${Math.abs(dI).toFixed(1)}%, рефлектограмма показывает отражение на расстоянии ${st.dist} м.`;
+      }
+    }
+  }
+
+  container.querySelectorAll('#s3-type button').forEach(btn => {
+    btn.addEventListener('click', () => {
+      container.querySelectorAll('#s3-type button').forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      st.type = btn.dataset.val;
+      render();
+    });
+  });
+  container.querySelector('#s3-R').addEventListener('input', render);
+  container.querySelector('#s3-dist').addEventListener('input', render);
+
+  currentRender = render;
+  render();
+  requestAnimationFrame(render);
+}
+
+function drawScene3(canvas, st, Gamma) {
+  const { ctx, w, h } = setupCanvas(canvas);
+  ctx.clearRect(0, 0, w, h);
+
+  const padL = 36, padR = 36;
+  const lineLeft = padL, lineRight = w - padR;
+  const lineLen = lineRight - lineLeft;
+  const lineY = h * 0.26;
+  const mToPx = (m) => lineLeft + (m / 1000) * lineLen;
+
+  // источник и нагрузка
+  drawSourceBox(ctx, lineLeft - 26, lineY - 15, 26, 30, 'И');
+  drawSourceBox(ctx, lineRight, lineY - 15, 26, 30, 'Н');
+
+  // линия (с разрывом при последовательном)
+  ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+  if (st.type === 'serial') {
+    const tx = mToPx(st.dist);
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(tx - 24, lineY); ctx.stroke();
+    ctx.beginPath(); ctx.moveTo(tx + 24, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+  } else {
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+  }
+
+  // точка подключения
+  if (st.type === 'parallel') {
+    const tx = mToPx(st.dist);
+    const tx1 = tx - 32, tx2 = tx + 32;
+    const yBot = lineY + 52;
+
+    // параллельный контур: два отвода вниз + перемычка с устройством
+    ctx.strokeStyle = '#b02a1e'; ctx.lineWidth = 2.5;
+    ctx.beginPath();
+    ctx.moveTo(tx1, lineY); ctx.lineTo(tx1, yBot);
+    ctx.lineTo(tx - 24, yBot); ctx.stroke();
+    ctx.beginPath();
+    ctx.moveTo(tx2, lineY); ctx.lineTo(tx2, yBot);
+    ctx.lineTo(tx + 24, yBot); ctx.stroke();
+
+    // устройство злоумышленника в перемычке
+    drawDevice(ctx, tx, yBot, 48, 24, 'ЗЛ', '#b02a1e');
+
+    // подписи: удалённость над линией, сопротивление под устройством
+    drawLabel(ctx, st.dist + ' м', tx, lineY - 16, 'center', 13, '#b02a1e');
+    drawLabel(ctx, 'R = ' + st.R + ' Ом', tx, yBot + 30, 'center', 13, '#b02a1e');
+
+  } else if (st.type === 'serial') {
+    const tx = mToPx(st.dist);
+
+    // устройство в разрыве линии
+    drawDevice(ctx, tx, lineY, 48, 28, 'ЗЛ', '#b02a1e');
+
+    drawLabel(ctx, st.dist + ' м', tx, lineY - 28, 'center', 13, '#b02a1e');
+    drawLabel(ctx, 'R = ' + st.R + ' Ом', tx, lineY + 40, 'center', 13, '#b02a1e');
+  }
+
+  // рефлектограмма
+  const roY0 = h * 0.60, roH = h * 0.36;
+  ctx.fillStyle = '#fbf7eb'; ctx.fillRect(lineLeft, roY0, lineLen, roH);
+  ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 1; ctx.strokeRect(lineLeft, roY0, lineLen, roH);
+
+  ctx.strokeStyle = 'rgba(26,39,68,0.1)'; ctx.lineWidth = 0.5;
+  for (let i = 1; i < 10; i++) {
+    const x = lineLeft + (i / 10) * lineLen;
+    ctx.beginPath(); ctx.moveTo(x, roY0); ctx.lineTo(x, roY0 + roH); ctx.stroke();
+  }
+  const midY = roY0 + roH / 2;
+  ctx.strokeStyle = 'rgba(26,39,68,0.35)'; ctx.lineWidth = 1;
+  ctx.beginPath(); ctx.moveTo(lineLeft, midY); ctx.lineTo(lineRight, midY); ctx.stroke();
+
+  if (st.type !== 'none' && Math.abs(Gamma) > 0.01) {
+    const px = mToPx(st.dist);
+    const amp = Math.abs(Gamma) * roH * 0.42;
+    const sign = Gamma < 0 ? -1 : 1;
+    const pw = Math.max(10, lineLen * 0.02);
+    ctx.fillStyle = Gamma < 0 ? '#b02a1e' : '#2d5a3d';
+    ctx.beginPath();
+    ctx.moveTo(px - pw, midY);
+    ctx.lineTo(px, midY + sign * amp);
+    ctx.lineTo(px + pw, midY);
+    ctx.lineTo(px, midY + sign * amp * 0.4);
+    ctx.closePath(); ctx.fill();
+    drawLabel(ctx, 't = ' + (2 * st.dist / 2e8 * 1e9).toFixed(0) + ' нс', px, roY0 - 8, 'center', 11);
+  }
+
+  drawLabel(ctx, 'Рефлектограмма', lineLeft + lineLen / 2, roY0 - 8, 'center', 12, '#4a5875');
+  drawLabel(ctx, '0 м', lineLeft + 4, roY0 + roH + 16, 'left', 12, '#4a5875');
+  drawLabel(ctx, '1000 м', lineRight - 4, roY0 + roH + 16, 'right', 12, '#4a5875');
+}
+
+/* ---------- Сцена 5. Целостность ---------- */
+function scene4(container) {
+  setTitle('Угроза целостности');
+  container.innerHTML = `
+    <h2>Угроза целостности: подмена сигнала</h2>
+    <p>Злоумышленник может не только слушать, но и <strong>говорить в линию</strong>. Для сети оповещения это худший сценарий: люди получают ложную команду. Сигнал в линии — суперпозиция полезного и постороннего.</p>
+
+    <div class="controls">
+      <div class="ctrl-row">
+        <label>Амплитуда врезки:</label>
+        <input type="range" id="s4-A" min="0" max="200" step="5" value="0">
+        <span class="val" id="s4-A-val">0 %</span>
+      </div>
+      <div class="ctrl-row">
+        <label>Частота врезки:</label>
+        <input type="range" id="s4-f" min="300" max="2000" step="10" value="1000">
+        <span class="val" id="s4-f-val">1000 Гц</span>
+      </div>
+      <div class="ctrl-row">
+        <label>Точка врезки:</label>
+        <input type="range" id="s4-dist" min="0" max="1000" step="10" value="500">
+        <span class="val" id="s4-dist-val">500 м</span>
+      </div>
+      <div class="seg" id="s4-auth">
+        <button data-val="off" class="active">Аутентификация выкл.</button>
+        <button data-val="on">Аутентификация вкл.</button>
+      </div>
+    </div>
+
+    <canvas class="viz" id="s4-canvas" style="height:300px;"></canvas>
+
+    <div class="readout">
+      <div class="item"><div class="lbl">Сигнал в приёмнике</div><div class="val" id="s4-state">ОПОВЕЩЕНИЕ</div></div>
+      <div class="item"><div class="lbl">A врезки / A полезного</div><div class="val" id="s4-snr">0.00</div></div>
+      <div class="item"><div class="lbl">Решение приёмника</div><div class="val" id="s4-dec">—</div></div>
+    </div>
+
+    <div class="feedback" id="s4-feedback">Увеличьте амплитуду врезки и посмотрите, что произойдёт.</div>`;
+
+  const st = { A2: 0, f2: 1000, dist: 500, auth: false };
+  const canvas = container.querySelector('#s4-canvas');
+  const A1 = 100, f1 = 1000;
+
+  function render() {
+    st.A2 = parseInt(container.querySelector('#s4-A').value);
+    st.f2 = parseInt(container.querySelector('#s4-f').value);
+    st.dist = parseInt(container.querySelector('#s4-dist').value);
+    container.querySelector('#s4-A-val').textContent = st.A2 + ' %';
+    container.querySelector('#s4-f-val').textContent = st.f2 + ' Гц';
+    container.querySelector('#s4-dist-val').textContent = st.dist + ' м';
+
+    const ratio = st.A2 / A1;
+    const sameFreq = Math.abs(st.f2 - f1) < 50;
+
+    let stateText, decision, cls = '';
+    let rejected = false;
+    if (st.A2 === 0) {
+      stateText = 'ОПОВЕЩЕНИЕ'; decision = '—';
+    } else if (st.auth) {
+      if (sameFreq && ratio < 1) { stateText = 'ОПОВЕЩЕНИЕ'; decision = 'ПРИНЯТ'; }
+      else { stateText = 'ОТКЛОНЁН'; decision = 'КОД НЕ СОВПАЛ'; rejected = true; }
+    } else {
+      if (ratio >= 1) { stateText = 'ЛОЖНЫЙ СИГНАЛ'; decision = '—'; cls = 'warn'; }
+      else if (ratio >= 0.3) { stateText = 'ИСКАЖЁН'; decision = '—'; cls = 'warn'; }
+      else { stateText = 'ОПОВЕЩЕНИЕ'; decision = '—'; }
+    }
+
+    const stEl = container.querySelector('#s4-state');
+    stEl.textContent = stateText;
+    stEl.className = 'val ' + (cls || (stateText === 'ОПОВЕЩЕНИЕ' ? 'ok' : ''));
+
+    container.querySelector('#s4-snr').textContent = ratio.toFixed(2);
+    const decEl = container.querySelector('#s4-dec');
+    decEl.textContent = decision;
+    decEl.className = 'val ' + (decision === 'ПРИНЯТ' ? 'ok' : (decision === 'КОД НЕ СОВПАЛ' ? 'warn' : ''));
+
+    const fb = container.querySelector('#s4-feedback');
+    if (st.A2 === 0) {
+      fb.innerHTML = 'Врезки нет, сигнал чистый. Приёмник видит только полезный сигнал.';
+    } else if (st.auth) {
+      if (stateText === 'ОПОВЕЩЕНИЕ') {
+        fb.innerHTML = '<strong>Аутентификация пропускает сигнал.</strong> Частота совпадает с ожидаемой, врезка слабее полезного сигнала. Приёмник не видит подмены.';
+      } else if (sameFreq) {
+        fb.innerHTML = '<strong>Аутентификация отклонила сигнал.</strong> Амплитуда врезки больше полезного сигнала — доминирующая частота изменилась. Приёмник это распознал.';
+      } else {
+        fb.innerHTML = '<strong>Аутентификация отклонила сигнал.</strong> В линии появилась посторонняя частота, отличная от ожидаемой. Приёмник проверяет код и не пропускает чужой сигнал.';
+      }
+    } else {
+      if (ratio >= 1) {
+        fb.innerHTML = '<strong>Полная маскировка.</strong> Амплитуда врезки больше полезного сигнала — приёмник «слышит» только злоумышленника. Люди получают ложное оповещение.';
+      } else if (ratio >= 0.3) {
+        fb.innerHTML = '<strong>Сигнал искажён.</strong> Врезка заметна, но не заглушает полезный сигнал. Приёмник видит смесь двух частот, разобрать трудно.';
+      } else {
+        fb.innerHTML = '<strong>Врезка мала.</strong> Она есть, но не влияет на приём. Приёмник не отличает её от шума.';
+      }
+    }
+
+    drawScene4(canvas, st, A1, f1, rejected);
+  }
+
+  container.querySelectorAll('#s4-auth button').forEach(btn => {
+    btn.addEventListener('click', () => {
+      container.querySelectorAll('#s4-auth button').forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      st.auth = btn.dataset.val === 'on';
+      render();
+    });
+  });
+  container.querySelector('#s4-A').addEventListener('input', render);
+  container.querySelector('#s4-f').addEventListener('input', render);
+  container.querySelector('#s4-dist').addEventListener('input', render);
+
+  currentRender = render;
+  render();
+  requestAnimationFrame(render);
+}
+
+function drawScene4(canvas, st, A1, f1, rejected) {
+  const { ctx, w, h } = setupCanvas(canvas);
+  ctx.clearRect(0, 0, w, h);
+
+  const padX = 40, gw = w - 2 * padX;
+  const rowH = (h - 10) / 3;
+  const A_total = A1 + st.A2;
+
+  const rows = [
+    { label: 'Полезный сигнал', color: '#2d5a3d', kind: 'u' },
+    { label: 'Врезка злоумышленника', color: '#b02a1e', kind: 'n' },
+    { label: 'Сигнал в приёмнике', color: '#1a2744', kind: 's' }
+  ];
+
+  rows.forEach((row, idx) => {
+    const top = idx * rowH;
+    const centerY = top + rowH / 2;
+    const amp = rowH * 0.38;
+
+    ctx.fillStyle = idx === 2 ? '#f3ecd8' : '#fbf7eb';
+    ctx.fillRect(padX, top, gw, rowH - 4);
+    ctx.strokeStyle = 'rgba(26,39,68,0.2)'; ctx.lineWidth = 1;
+    ctx.strokeRect(padX, top, gw, rowH - 4);
+
+    ctx.strokeStyle = 'rgba(26,39,68,0.3)';
+    ctx.beginPath(); ctx.moveTo(padX, centerY); ctx.lineTo(padX + gw, centerY); ctx.stroke();
+
+    ctx.fillStyle = row.color;
+    ctx.font = 'bold 10px "Oswald", sans-serif';
+    ctx.textAlign = 'left';
+    ctx.fillText(row.label, padX + 6, top + 14);
+
+    if (idx === 1 && st.A2 === 0) return;
+
+    const tx = padX + (st.dist / 1000) * gw;
+    const periods = 3;
+    const samples = 300;
+    const scale = amp / Math.max(1, A_total);
+
+    ctx.strokeStyle = row.color; ctx.lineWidth = 1.5;
+    ctx.beginPath();
+
+    let started = false;
+
+    for (let i = 0; i <= samples; i++) {
+      const x = padX + (i / samples) * gw;
+      const t = (i / samples) * periods / f1;
+
+      if (row.kind === 'u') {
+        const val = A1 * Math.sin(2 * Math.PI * f1 * t);
+        const y = centerY - val * scale;
+        if (!started) { ctx.moveTo(x, y); started = true; }
+        else ctx.lineTo(x, y);
+      } else if (row.kind === 'n') {
+        if (x < tx) continue;
+        const val = st.A2 * Math.sin(2 * Math.PI * st.f2 * t);
+        const y = centerY - val * scale;
+        if (!started) { ctx.moveTo(x, y); started = true; }
+        else ctx.lineTo(x, y);
+      } else {
+        // s
+        if (x < tx) {
+          const val = A1 * Math.sin(2 * Math.PI * f1 * t);
+          const y = centerY - val * scale;
+          if (!started) { ctx.moveTo(x, y); started = true; }
+          else ctx.lineTo(x, y);
+        } else {
+          if (rejected) {
+            if (!started) { ctx.moveTo(x, centerY); started = true; }
+            else ctx.lineTo(x, centerY);
+          } else {
+            const val = A1 * Math.sin(2 * Math.PI * f1 * t) + st.A2 * Math.sin(2 * Math.PI * st.f2 * t);
+            const y = centerY - val * scale;
+            if (!started) { ctx.moveTo(x, y); started = true; }
+            else ctx.lineTo(x, y);
+          }
+        }
+      }
+    }
+    ctx.stroke();
+  });
+
+  // вертикальная отметка точки врезки
+  if (st.A2 > 0) {
+    const tx = padX + (st.dist / 1000) * gw;
+    ctx.strokeStyle = 'rgba(176,42,30,0.5)'; ctx.lineWidth = 1;
+    ctx.setLineDash([4, 4]);
+    ctx.beginPath(); ctx.moveTo(tx, 0); ctx.lineTo(tx, h); ctx.stroke();
+    ctx.setLineDash([]);
+  }
+
+  // надпись "ОТКЛОНЁН" при отклонении
+  if (rejected) {
+    const tx = padX + (st.dist / 1000) * gw;
+    const rowTop = 2 * rowH;
+    ctx.fillStyle = '#b02a1e';
+    ctx.font = 'bold 12px "Oswald", sans-serif';
+    ctx.textAlign = 'left';
+    ctx.fillText('ОТКЛОНЁН', tx + 8, rowTop + 20);
+  }
+}
+
+/* ---------- Сцена 6. Доступность ---------- */
+function scene5(container) {
+  setTitle('Угроза доступности');
+  container.innerHTML = `
+    <h2>Угроза доступности: обрыв и подавление</h2>
+    <p>Линию берегут именно за доступность — и её проще всего уничтожить физически. Обрыв, короткое замыкание или мощная помеха прерывают связь. Один и тот же рефлектометр видит и подключение (К), и обрыв (Д).</p>
+
+    <div class="controls">
+      <div class="seg" id="s5-type">
+        <button data-val="none" class="active">Нет</button>
+        <button data-val="break">Обрыв</button>
+        <button data-val="short">КЗ</button>
+        <button data-val="interf">Помеха</button>
+      </div>
+      <div class="ctrl-row">
+        <label>Точка воздействия:</label>
+        <input type="range" id="s5-dist" min="0" max="1000" step="10" value="500">
+        <span class="val" id="s5-dist-val">500 м</span>
+      </div>
+      <div class="seg" id="s5-backup">
+        <button data-val="off" class="active">Без резерва</button>
+        <button data-val="on">С резервом</button>
+      </div>
+    </div>
+
+    <canvas class="viz" id="s5-canvas" style="height:240px;"></canvas>
+
+    <div class="readout">
+      <div class="item"><div class="lbl">Доступность линии</div><div class="val" id="s5-state">НОРМА</div></div>
+      <div class="item"><div class="lbl">Абонентов без сигнала</div><div class="val" id="s5-affected">0</div></div>
+    </div>
+
+    <div class="feedback" id="s5-feedback">Выберите тип воздействия.</div>`;
+
+  const st = { type: 'none', dist: 500, backup: false };
+  const canvas = container.querySelector('#s5-canvas');
+  const houses = [50, 200, 350, 500, 650, 800, 950];
+
+  function render() {
+    st.dist = parseInt(container.querySelector('#s5-dist').value);
+    container.querySelector('#s5-dist-val').textContent = st.dist + ' м';
+
+    let affected = 0, stateText, cls = '';
+    if (st.type === 'none') { affected = 0; stateText = 'НОРМА'; cls = 'ok'; }
+    else if (st.type === 'break') {
+      if (st.backup) { affected = 0; stateText = 'ОБХОД'; cls = 'ok'; }
+      else { affected = houses.filter(m => m > st.dist).length; stateText = 'ОБРЫВ'; cls = 'warn'; }
+    } else if (st.type === 'short') { affected = houses.length; stateText = 'КЗ'; cls = 'warn'; }
+    else { affected = houses.filter(m => m > st.dist).length; stateText = 'ПОМЕХА'; cls = 'warn'; }
+
+    const stEl = container.querySelector('#s5-state');
+    stEl.textContent = stateText;
+    stEl.className = 'val ' + cls;
+    container.querySelector('#s5-affected').textContent = affected;
+
+    const fb = container.querySelector('#s5-feedback');
+    if (st.type === 'none') fb.innerHTML = 'Все абоненты получают сигнал. Линия в норме.';
+    else if (st.type === 'break') {
+      if (st.backup) fb.innerHTML = '<strong>Резервный маршрут сработал.</strong> Сигнал обошёл место обрыва. Резервирование — единственное средство, которое работает против обрыва. Обнаружение обрыва — не защита, а сигнал к действию.';
+      else fb.innerHTML = `<strong>Обрыв на ${st.dist} м.</strong> Абоненты дальше точки обрыва потеряли сигнал: их ${affected}. В линейной топологии один разрез убивает часть сети.`;
+    } else if (st.type === 'short') {
+      fb.innerHTML = '<strong>Короткое замыкание.</strong> Линия «села», сигнал пропал у всех абонентов. Это не съём, а авария — но система контроля должна её видеть. Резерв не спасает от КЗ.';
+    } else {
+      fb.innerHTML = `<strong>Мощная помеха на ${st.dist} м.</strong> Сигнал есть, но разобрать его нельзя. Абонентов после точки помехи без связи: ${affected}. Против помехи работает не резервирование, а помехоустойчивость.`;
+    }
+
+    drawScene5(canvas, st, houses);
+  }
+
+  container.querySelectorAll('#s5-type button').forEach(btn => {
+    btn.addEventListener('click', () => {
+      container.querySelectorAll('#s5-type button').forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      st.type = btn.dataset.val;
+      render();
+    });
+  });
+  container.querySelectorAll('#s5-backup button').forEach(btn => {
+    btn.addEventListener('click', () => {
+      container.querySelectorAll('#s5-backup button').forEach(b => b.classList.remove('active'));
+      btn.classList.add('active');
+      st.backup = btn.dataset.val === 'on';
+      render();
+    });
+  });
+  container.querySelector('#s5-dist').addEventListener('input', render);
+
+  currentRender = render;
+  render();
+  requestAnimationFrame(render);
+}
+
+function drawScene5(canvas, st, houses) {
+  const { ctx, w, h } = setupCanvas(canvas);
+  ctx.clearRect(0, 0, w, h);
+
+  const padL = 30, padR = 30;
+  const lineLeft = padL, lineRight = w - padR;
+  const lineLen = lineRight - lineLeft;
+  const lineY = h * 0.55;
+  const mToPx = (m) => lineLeft + (m / 1000) * lineLen;
+
+  // резерв
+  if (st.backup) {
+    ctx.strokeStyle = '#2d5a3d'; ctx.lineWidth = 2; ctx.setLineDash([5, 3]);
+    ctx.beginPath();
+    ctx.moveTo(lineLeft, lineY);
+    ctx.bezierCurveTo(lineLeft + lineLen * 0.3, lineY - 50, lineRight - lineLen * 0.3, lineY - 50, lineRight, lineY);
+    ctx.stroke();
+    ctx.setLineDash([]);
+    ctx.fillStyle = '#2d5a3d'; ctx.font = '10px "Roboto Mono", monospace';
+    ctx.textAlign = 'center';
+    ctx.fillText('резервный маршрут', (lineLeft + lineRight) / 2, lineY - 55);
+  }
+
+  // линия
+  if (st.type === 'break') {
+    ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(mToPx(st.dist) - 8, lineY); ctx.stroke();
+    ctx.beginPath(); ctx.moveTo(mToPx(st.dist) + 8, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+    ctx.strokeStyle = '#b02a1e'; ctx.lineWidth = 3;
+    const bx = mToPx(st.dist);
+    ctx.beginPath(); ctx.moveTo(bx - 8, lineY - 10); ctx.lineTo(bx - 8, lineY + 10); ctx.stroke();
+    ctx.beginPath(); ctx.moveTo(bx + 8, lineY - 10); ctx.lineTo(bx + 8, lineY + 10); ctx.stroke();
+  } else if (st.type === 'short') {
+    ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+    const x = mToPx(st.dist);
+    ctx.strokeStyle = '#b02a1e'; ctx.lineWidth = 2;
+    ctx.beginPath(); ctx.moveTo(x, lineY); ctx.lineTo(x, lineY + 25); ctx.stroke();
+    ctx.beginPath(); ctx.moveTo(x - 10, lineY + 25); ctx.lineTo(x + 10, lineY + 25); ctx.stroke();
+    ctx.fillStyle = '#b02a1e'; ctx.font = 'bold 11px "Roboto Mono", monospace';
+    ctx.textAlign = 'center'; ctx.fillText('КЗ', x, lineY + 42);
+  } else if (st.type === 'interf') {
+    ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(mToPx(st.dist), lineY); ctx.stroke();
+    ctx.strokeStyle = '#b02a1e'; ctx.lineWidth = 2;
+    ctx.beginPath();
+    const x0 = mToPx(st.dist), x1 = lineRight;
+    for (let i = 0; i <= 100; i++) {
+      const x = x0 + (i / 100) * (x1 - x0);
+      const y = lineY + Math.sin(i * 0.6) * 3;
+      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
+    }
+    ctx.stroke();
+  } else {
+    ctx.strokeStyle = '#1a2744'; ctx.lineWidth = 3;
+    ctx.beginPath(); ctx.moveTo(lineLeft, lineY); ctx.lineTo(lineRight, lineY); ctx.stroke();
+  }
+
+  // дома
+  houses.forEach(m => {
+    const x = mToPx(m);
+    const afterBreak = st.type === 'break' && m > st.dist;
+    const afterShort = st.type === 'short';
+    const afterInterf = st.type === 'interf' && m > st.dist;
+    const bad = (afterBreak || afterShort || afterInterf) && !(st.backup && st.type === 'break');
+
+    ctx.fillStyle = bad ? '#b02a1e' : '#1a2744';
+    ctx.fillRect(x - 10, lineY - 24, 20, 16);
+    ctx.beginPath();
+    ctx.moveTo(x - 12, lineY - 24); ctx.lineTo(x, lineY - 33); ctx.lineTo(x + 12, lineY - 24);
+    ctx.closePath(); ctx.fill();
+
+    if (bad) {
+      ctx.strokeStyle = '#b02a1e'; ctx.lineWidth = 2;
+      ctx.beginPath(); ctx.moveTo(x - 5, lineY - 45); ctx.lineTo(x + 5, lineY - 35);
+      ctx.moveTo(x + 5, lineY - 45); ctx.lineTo(x - 5, lineY - 35); ctx.stroke();
+    } else {
+      ctx.strokeStyle = '#2d5a3d'; ctx.lineWidth = 1.5;
+      ctx.beginPath(); ctx.arc(x, lineY - 45, 4, -Math.PI, 0); ctx.stroke();
+      ctx.beginPath(); ctx.arc(x, lineY - 45, 8, -Math.PI, 0); ctx.stroke();
+    }
+  });
+
+  ctx.fillStyle = '#4a5875'; ctx.font = '11px "Roboto Mono", monospace';
+  ctx.textAlign = 'left'; ctx.fillText('0 м', lineLeft, lineY + 60);
+  ctx.textAlign = 'right'; ctx.fillText('1000 м', lineRight, lineY + 60);
+  ctx.textAlign = 'center'; ctx.fillText('Линейная топология' + (st.backup ? ' + кольцевой резерв' : ''), (lineLeft + lineRight) / 2, 20);
+}
+
+/* ---------- Сцена 7. Применение ---------- */
+function scene6(container) {
+  setTitle('Средства защиты по триаде');
+  container.innerHTML = `
+    <h2>Применение: средства защиты по триаде</h2>
+    <p>Один и тот же инструмент может закрывать разные угрозы. Нажмите на угрозу — раскроются средства защиты.</p>
+
+    <div class="triad" id="s6-triad">
+      <div class="card active" data-key="k"><span class="icon">👁</span><h4>Конфиденциальность</h4><p>Найти подключение и не дать ему работать</p></div>
+      <div class="card" data-key="c"><span class="icon">✎</span><h4>Целостность</h4><p>Отличить «свой» сигнал от «чужого»</p></div>
+      <div class="card" data-key="d"><span class="icon">⛓</span><h4>Доступность</h4><p>Сохранить путь сигнала или восстановить</p></div>
+    </div>
+
+    <div id="s6-content"></div>`;
+
+  const data = {
+    k: {
+      title: 'Конфиденциальность',
+      items: [
+        ['Рефлектометрия (TDR)', 'Находит неоднородность в линии: параллельное подключение даёт отрицательное отражение.'],
+        ['Контроль импеданса и тока', 'Сравнение с эталоном выявляет подключение: импеданс падает, ток растёт.'],
+        ['«Выжигание»', 'Высоковольтный импульс в линию выводит из строя параллельно подключённое устройство.'],
+        ['Пломбирование и контроль трасс', 'Не даёт физически добраться до линии — организационная защита.']
+      ]
+    },
+    c: {
+      title: 'Целостность',
+      items: [
+        ['Аутентификация сигнала', 'Тональный код, последовательность посылок — приёмник проверяет «тот или не тот» сигнал.'],
+        ['Контроль уровня', 'Слишком сильный сигнал, доминирующая посторонняя частота — признаки врезки.'],
+        ['Проверка на посторонние несущие', 'В линии не должно быть частот, отличных от рабочих.'],
+        ['Организационный контроль', 'Если злоумышленник знает код — это утечка регламента, а не техники.']
+      ]
+    },
+    d: {
+      title: 'Доступность',
+      items: [
+        ['Резервирование маршрутов', 'Кольцевая топология: при обрыве сигнал идёт в обход.'],
+        ['Мониторинг целостности', 'Рефлектометр видит обрыв так же, как и подключение.'],
+        ['Автономное питание', 'Линия питается от самой себя — уже часть защиты доступности.'],
+        ['Обходы и патрулирование', 'Организационная защита трасс от физического обрыва.']
+      ]
+    }
+  };
+
+  const content = container.querySelector('#s6-content');
+
+  function show(key) {
+    const d = data[key];
+    let html = `<h3>${d.title}: средства защиты</h3><ul style="padding-left:20px;">`;
+    d.items.forEach(([name, desc]) => { html += `<li style="margin:8px 0;"><strong>${name}.</strong> ${desc}</li>`; });
+    html += '</ul>';
+    if (key === 'k' || key === 'd') {
+      html += `<div class="feedback"><strong>Обрати внимание:</strong> рефлектометр стоит в двух строках. Для конфиденциальности он ищет подключение, для доступности — обрыв. Инструмент один, угрозы разные.</div>`;
+    }
+    if (key === 'd') {
+      html += `<div class="feedback"><strong>Связь с новостью:</strong> проводную сеть оповещения сохраняют именно за доступность. Но та же сеть физически доступна — значит, доступность защищают не только от аварий, но и от намеренного обрыва.</div>`;
+    }
+    content.innerHTML = html;
+  }
+
+  container.querySelectorAll('#s6-triad .card').forEach(card => {
+    card.addEventListener('click', () => {
+      container.querySelectorAll('#s6-triad .card').forEach(c => c.classList.remove('active'));
+      card.classList.add('active');
+      show(card.dataset.key);
+    });
+  });
+  show('k');
+}
+
+/* ---------- Сцена 8. Тест ---------- */
+function scene7(container) {
+  setTitle('Проверка понимания');
+  const questions = [
+    {
+      q: 'Параллельное подключение изменило ток в линии на 6%. Что нарушено?',
+      opts: ['Конфиденциальность', 'Целостность', 'Доступность', 'Ничего'],
+      correct: 0,
+      explain: 'Параллельное подключение = кто-то читает сигнал. Сигнал доходит до получателя неизменным (целостность цела), доступность не нарушена. Нарушена конфиденциальность.'
+    },
+    {
+      q: 'В линию внедрён сигнал с посторонней частотой. Аутентификация включена. Что произойдёт?',
+      opts: ['Сигнал будет отклонён как чужой', 'Сигнал пройдёт без изменений', 'Линия оборвётся', 'Приёмник сгорит'],
+      correct: 0,
+      explain: 'Аутентификация проверяет код (частоту, последовательность). Посторонняя частота — отклонение.'
+    },
+    {
+      q: 'Аутентификация не спасла от врезки, потому что злоумышленник знал код. Это…',
+      opts: ['Техническая уязвимость', 'Утечка регламента', 'Отказ оборудования', 'Ошибка проектирования'],
+      correct: 1,
+      explain: 'Если код известен — защита переносится в организационную плоскость. Это уже не техника, а регламент доступа к документации.'
+    },
+    {
+      q: 'Рефлектометр показывает неоднородность на 300 м. Это…',
+      opts: ['Только подключение', 'Только обрыв', 'И подключение, и обрыв', 'Ошибка прибора'],
+      correct: 2,
+      explain: 'Метод видит любую неоднородность. Характер отражения (положительное или отрицательное) и дополнительные измерения помогают различить.'
+    },
+    {
+      q: 'Какое средство работает против обрыва, а какое — только сообщает о нём?',
+      opts: ['Резервирование работает, мониторинг сообщает', 'Мониторинг работает, резервирование сообщает', 'Оба работают', 'Оба только сообщают'],
+      correct: 0,
+      explain: 'Резервирование сохраняет связь (обходит обрыв). Мониторинг только обнаруживает — это сигнал к действию, не защита.'
+    },
+    {
+      q: 'Почему проводное радиовещание устойчиво к кибератакам, но уязвимо по всем трём осям ИБ?',
+      opts: ['Не зависит от цифровых сетей и электричества, но физически доступно', 'Оно не использует электричество', 'В нём нет информации', 'Оно защищено государством'],
+      correct: 0,
+      explain: 'Энергонезависимость — защита от кибератак и аварий электросети. Но провод физически доступен: можно подслушать (К), подменить сигнал (Ц), оборвать (Д).'
+    }
+  ];
+
+  container.innerHTML = `
+    <h2>Проверка понимания</h2>
+    <p>Шесть вопросов по трём угрозам. После ответа — пояснение.</p>
+    <div id="s7-questions"></div>
+    <div class="checklist" id="s7-checklist" style="display:none;">
+      <h3>Чек-лист конспекта</h3>
+      <ul>
+        <li>Определения: проводной канал, конфиденциальность, целостность, доступность, параллельное/последовательное подключение, рефлектометрия.</li>
+        <li>Формула: L = v · t / 2, Γ = (Z_н − Z₀) / (Z_н + Z₀).</li>
+        <li>Таблица «угроза → средство».</li>
+        <li>Три крайних случая: короткое замыкание (К), совпадение кода (Ц), обрыв в начале линии (Д).</li>
+      </ul>
+    </div>`;
+
+  const qs = container.querySelector('#s7-questions');
+  let answered = 0;
+
+  questions.forEach((q, qi) => {
+    const item = document.createElement('div');
+    item.className = 'test-item';
+    item.innerHTML = `<div class="q">${qi + 1}. ${q.q}</div><div class="opts">${q.opts.map((opt, oi) => `<div class="opt" data-oi="${oi}">${opt}</div>`).join('')}</div><div class="explain">${q.explain}</div>`;
+    qs.appendChild(item);
+
+    let done = false;
+    item.querySelectorAll('.opt').forEach(opt => {
+      opt.addEventListener('click', () => {
+        if (done) return;
+        done = true;
+        const oi = parseInt(opt.dataset.oi);
+        item.querySelectorAll('.opt').forEach((o, idx) => {
+          o.classList.add('disabled');
+          if (idx === q.correct) o.classList.add('correct');
+          else if (idx === oi) o.classList.add('wrong');
+        });
+        item.querySelector('.explain').classList.add('visible');
+        answered++;
+        if (answered === questions.length) {
+          container.querySelector('#s7-checklist').style.display = 'block';
+        }
+      });
+    });
+  });
+}
+
+/* ---------- Сцена 9. Итог ---------- */
+function scene8(container) {
+  setTitle('Итог');
+  container.innerHTML = `
+    <h2>Итог</h2>
+    <p>Линия под контролем — это не один прибор, а система. У каждой угрозы своё средство, и часто они пересекаются.</p>
+
+    <div class="checklist">
+      <h3>Что вы теперь умеете</h3>
+      <ul>
+        <li>Объяснять, почему проводной канал важен даже в цифровую эпоху: энергонезависимость и независимость от цифровых сетей.</li>
+        <li>Различать три угрозы ИБ применительно к проводной линии — конфиденциальность, целостность, доступность.</li>
+        <li>Для каждой угрозы называть способ атаки и средство защиты.</li>
+        <li>Объяснять, что один инструмент (рефлектометр) может закрывать разные угрозы.</li>
+      </ul>
+    </div>
+
+    <div class="defs-box">
+      <dl>
+        <dt>Главное различие</dt>
+        <dd><strong>К:</strong> сигнал прочитан. <strong>Ц:</strong> сигнал изменён. <strong>Д:</strong> сигнал не дошёл. Одно подключение — это К. Врезка с подменой — это Ц. Обрыв — это Д. Три разных средства, три разных метода обнаружения.</dd>
+      </dl>
+    </div>
+
+    <p style="margin-top:24px;color:#4a5875;font-size:14px;">Тема 4.2, ПМ.03.01. Интерактив собран по методике «Заслон»: спецификация → генерация → верификация → защита. Модель не заменяет реальный прибор: она показывает физику, с которой прибор работает.</p>`;
+}
+
+/* ---------- Навигация ---------- */
+const scenes = [scene0, scene1, scene2, scene3, scene4, scene5, scene6, scene7, scene8];
+let current = 0;
+
+function showScene(i) {
+  stopAnim();
+  currentRender = null;
+  current = Math.max(0, Math.min(scenes.length - 1, i));
+  const main = document.getElementById('main');
+  main.innerHTML = '';
+  scenes[current](main);
+
+  // нумерация в шапке
+  document.getElementById('sceneNum').textContent = 'Сцена ' + (current + 1) + ' из ' + scenes.length;
+
+  // префикс "Сцена N." к заголовку h2 (кроме сцены 0, где h1)
+  const h2 = main.querySelector('h2');
+  if (h2 && !/^Сцена\s/.test(h2.textContent)) {
+    h2.textContent = 'Сцена ' + (current + 1) + '. ' + h2.textContent;
+  }
+
+  document.getElementById('counter').textContent = (current + 1) + ' / ' + scenes.length;
+  document.getElementById('prevBtn').disabled = current === 0;
+  document.getElementById('nextBtn').disabled = current === scenes.length - 1;
+  document.getElementById('progressFill').style.width = ((current + 1) / scenes.length * 100) + '%';
+
+  window.scrollTo(0, 0);
+}
+
+document.getElementById('prevBtn').addEventListener('click', () => showScene(current - 1));
+document.getElementById('nextBtn').addEventListener('click', () => showScene(current + 1));
+
+let resizeTimer;
+window.addEventListener('resize', () => {
+  clearTimeout(resizeTimer);
+  resizeTimer = setTimeout(() => { if (currentRender) currentRender(); }, 150);
+});
+
+showScene(0);
+</script>
+</body>
+</html>

+ 246 - 0
ПМ.03.01/4.2/001/specification.md

@@ -0,0 +1,246 @@
+# ЛИСТ СПЕЦИФИКАЦИИ (Базовый трек)
+
+**Предмет:** ТЗИ (ПМ.03.01)
+**Тема по программе:** 4.2 Системы защиты от утечки информации по проводному каналу.
+**Название интерактива:** Линия под контролем. Три угрозы ИБ на проводном канале: конфиденциальность, целостность, доступность.
+**Формат:** учебный интерактив (не студенческий проект)
+**Автор:** преподаватель (не студент)
+
+---
+
+## 1. Содержательная основа
+
+### 1.1. Какие понятия/термины/процессы проверяет интерактив?
+
+**Актуализация (сцена 2):**
+
+- энергонезависимость проводного радиовещания и линий оповещения: питание от линии, работа при отключении электричества и падении сотовой сети;
+- обратная сторона энергонезависимости: физическая доступность провода на всей длине (щиток, подвал, чердак, кабельная трасса);
+- триада КЦД применительно к проводной линии: конфиденциальность (сигнал прочитан), целостность (сигнал изменён), доступность (сигнал не дошёл).
+
+**Определения (сцена 3):**
+
+- проводной канал утечки;
+- конфиденциальность, целостность, доступность;
+- параллельное подключение (дополнительная нагрузка, меняет ток и импеданс);
+- последовательное подключение (разрыв с вставкой, меняет сопротивление участка);
+- рефлектометрия (TDR): обнаружение неоднородностей по отражённому импульсу.
+
+**Новая тема (сцены 4–6):**
+
+- параллельное подключение: \(Z_t = \frac{R \cdot Z_0}{R + Z_0}\), ток в линии растёт при уменьшении \(R\); при \(R \to 0\) — короткое замыкание, авария, а не скрытый съём;
+- последовательное подключение: \(Z = Z_0 + R\), ток падает; при малом \(R\) вставка почти невидима по току, но видна рефлектометру;
+- рефлектометрия: коэффициент отражения \(\Gamma = \frac{Z_н - Z_0}{Z_н + Z_0}\), расстояние \(L = \frac{v \cdot t}{2}\); положительное и отрицательное отражение, различение подключения и обрыва;
+- целостность: суперпозиция полезного сигнала и врезки \(S(t) = A_1\sin(2\pi f_1 t) + A_2\sin(2\pi f_2 t + \varphi)\); аутентификация сигнала по частоте и амплитуде; случай совпадения кода — утечка регламента, а не техника;
+- доступность: обрыв, короткое замыкание, мощная помеха; линейная и кольцевая топология; резервирование как единственное средство против обрыва; мониторинг как обнаружение, а не защита.
+
+**Применение и ИБ (сцена 7):**
+
+- средства защиты по триаде: рефлектометрия, контроль импеданса и тока, «выжигание», пломбирование трасс (К); аутентификация сигнала, контроль уровня, проверка на посторонние несущие (Ц); резервирование маршрутов, мониторинг целостности, автономное питание, обходы и патрулирование (Д);
+- один инструмент (рефлектометр) обслуживает две угрозы: ищет подключение (К) и обрыв (Д);
+- связь с новостью: проводную сеть оповещения сохраняют за доступность, но та же сеть физически доступна — значит, доступность защищают не только от аварий, но и от намеренного обрыва.
+
+### 1.2. Какие вопросы/задания/сюжетные ходы используются?
+
+**Сценарий:** цель → актуализация (энергонезависимость и физический доступ, триада) → определения → конфиденциальность (подключение) → целостность (подмена сигнала) → доступность (обрыв и подавление) → применение (средства по триаде) → тест (6 вопросов) → итог.
+
+**Интерактивные элементы:**
+
+- сцена 2 — ползунок «расстояние до щитка», появление точек доступа на схеме линии, обратная связь по каждой точке;
+- сцена 4 — переключатель «нет / параллельное / последовательное», ползунки \(R\) злоумышленника и удалённости, три readout (ток, импеданс, отражение), рефлектограмма с отметкой времени;
+- сцена 5 — ползунки амплитуды, частоты и точки врезки, переключатель аутентификации, три графика (полезный сигнал, врезка, сигнал в приёмнике), readout состояния приёмника и решения;
+- сцена 6 — переключатель «нет / обрыв / КЗ / помеха», ползунок точки воздействия, переключатель «без резерва / с резервом», readout доступности и числа абонентов без сигнала;
+- сцена 7 — три карточки угроз, при нажатии раскрываются средства защиты; подчёркнуто, что рефлектометр стоит в двух строках;
+- сцена 8 — тест на 6 вопросов с пояснениями после каждого ответа и чек-листом конспекта в конце.
+
+**Обратная связь** объясняет закон, а не констатирует факт: «параллельное подключение шунтирует линию: сопротивление падает, ток растёт на X%», «аутентификация проверяет код, а не уровень», «резервирование сохраняет связь, мониторинг только обнаруживает».
+
+**Крайние случаи** проверяются в коде и осмыслены в обратной связи:
+
+- \(R \to 0\) при параллельном подключении — короткое замыкание, авария, система контроля должна сработать;
+- \(R\) велико — подключение маскируется, ток меняется меньше чем на 5%, нужен точный контроль импеданса или рефлектометрия;
+- врезка с посторонней частотой при включённой аутентификации — отклонение;
+- врезка с совпадением кода — аутентификация не спасает, защита переходит в организационную плоскость;
+- обрыв в начале линии — мертва вся линейная сеть; обрыв при включённом резерве — обход, 0 пострадавших; КЗ и помеха резервом не лечатся;
+- рефлектометр показывает неоднородность — это может быть и подключение, и обрыв, различение по характеру отражения и дополнительным измерениям.
+
+### 1.3. Откуда взято содержание?
+
+- Лекция по теме 4.2 «Системы защиты от утечки информации по проводному каналу» (ПМ.03.01).
+- Новость РБК о сохранении и развитии сетей проводного радиовещания как энергонезависимых и защищённых от кибератак — рамка для сцены актуализации и связи с доступностью.
+- Учебник/пособие по ТЗИ (уточнить выходные данные при финализации).
+- Теория линий передач: коэффициент отражения, рефлектометрия, согласование (общий курс физики/электротехники).
+- ГОСТ/нормативные документы по защите информации от утечки по техническим каналам (уточнить при финализации).
+- Триада КЦД — по ГОСТ Р 50922-2006 и учебным материалам по ИБ.
+
+**Терминологическая оговорка:** диктофон в теме 4.2 не рассматривается. По физике он относится к акустическому каналу (негласная запись речи в помещении). В программе он стоит в разделе «проводной канал» из-за методической небрежности; в интерактиве это противоречие не воспроизводится. В теме 4.2 рассматривается только то, что действительно связано с проводной линией: подключение к линии, врезка в линию, обрыв линии.
+
+---
+
+## 2. Сценарий
+
+**Сцена 1. Цель.**
+Проверяемая цель: после прохождения студент для каждой из трёх угроз ИБ называет способ атаки на проводную линию и средство защиты, а также объясняет, почему один прибор может обслуживать две угрозы.
+
+**Сцена 2. Актуализация: зачем линии, когда есть цифра.**
+Тезисы: проводное радиовещание и линии оповещения энергонезависимы, работают при отключении электричества и падении сотовой сети; но та же энергонезависимость означает физическую доступность провода.
+Интерактив: ползунок «расстояние до щитка» (0–1000 м); на схеме линии появляются точки доступа — щиток, подвал, чердак, кабельная трасса. Обратная связь: чем длиннее линия, тем больше физических точек; защита начинается с контроля всей длины, а не только квартиры.
+Внизу — три карточки триады: К, Ц, Д.
+
+**Сцена 3. Определения для тетради.**
+Блок «ЗАПИШИ В ТЕТРАДЬ»: проводной канал утечки, конфиденциальность, целостность, доступность, параллельное подключение, последовательное подключение, рефлектометрия (TDR).
+
+**Сцена 4. Угроза конфиденциальности: подключение к линии.**
+Переключатель «нет / параллельное / последовательное». Ползунок \(R\) злоумышленника (1–1000 Ом), ползунок удалённости (50–950 м).
+Схема: источник, нагрузка, линия; при параллельном подключении — два отвода вниз и перемычка с устройством «ЗЛ»; при последовательном — устройство в разрыве линии. Подписи удалённости и сопротивления — крупно, на подложке.
+Readout: ток в линии, импеданс, коэффициент отражения \(\Gamma\).
+Рефлектограмма: отметка отражения на расстоянии \(L = v \cdot t / 2\), положительное или отрицательное отражение.
+Обратная связь: при малом \(R\) — короткое замыкание (авария); при большом \(R\) — маскировка (ток меняется меньше чем на 5%); при последовательном подключении — ток падает, рефлектометр видит неоднородность.
+
+**Сцена 5. Угроза целостности: подмена сигнала.**
+Ползунки: амплитуда врезки (0–200% от полезного), частота врезки (300–2000 Гц), точка врезки (0–1000 м). Переключатель аутентификации (вкл/выкл).
+Три графика: полезный сигнал, врезка злоумышленника, сигнал в приёмнике (сумма). До точки врезки в приёмнике — чистый полезный сигнал; после — сумма. При включённой аутентификации и отклонении после точки врезки — горизонтальная линия на нуле и надпись «ОТКЛОНЁН».
+Readout: состояние сигнала в приёмнике (ОПОВЕЩЕНИЕ / ИСКАЖЁН / ЛОЖНЫЙ СИГНАЛ / ОТКЛОНЁН), отношение амплитуд, решение приёмника (ПРИНЯТ / КОД НЕ СОВПАЛ).
+Обратная связь: при амплитуде врезки больше полезного — полная маскировка; при совпадении частоты и кода — аутентификация не спасает, это утечка регламента.
+
+**Сцена 6. Угроза доступности: обрыв и подавление.**
+Переключатель «нет / обрыв / КЗ / помеха». Ползунок точки воздействия (0–1000 м). Переключатель «без резерва / с резервом».
+Схема: линия с абонентами; при обрыве — разрыв, абоненты после точки обрыва без сигнала; при КЗ — линия «села», без сигнала все; при помехе — сигнал есть, но не читается; при включённом резерве — обходной маршрут.
+Readout: доступность линии (НОРМА / ОБРЫВ / ОБХОД / КЗ / ПОМЕХА), число абонентов без сигнала.
+Обратная связь: резервирование — единственное средство против обрыва; мониторинг только обнаруживает; КЗ и помеха резервом не лечатся; против помехи работает помехоустойчивость.
+
+**Сцена 7. Применение: средства защиты по триаде.**
+Три карточки: К, Ц, Д. При нажатии раскрывается список средств защиты. Под таблицей — ремарка: рефлектометр стоит в двух строках, для К он ищет подключение, для Д — обрыв. Для Д — связь с новостью: сеть сохраняют за доступность, но она физически доступна, поэтому доступность защищают и от намеренного обрыва.
+
+**Сцена 8. Проверка понимания.**
+6 вопросов с пояснениями: по одному на каждую угрозу, два крайних случая (совпадение кода, различение подключения и обрыва по рефлектограмме) и один сводный (почему радиовещание устойчиво к кибератакам, но уязвимо по всем трём осям). После ответа — пояснение. В конце — чек-лист конспекта.
+
+**Сцена 9. Итог.**
+Чек-лист «Что вы теперь умеете», блок «Главное различие» (К — прочитан, Ц — изменён, Д — не дошёл), указание, что модель не заменяет реальный прибор.
+
+---
+
+## 3. Проверка содержания
+
+**Что проверено:**
+
+- Формула коэффициента отражения \(\Gamma = \frac{Z_н - Z_0}{Z_н + Z_0}\) и расстояния \(L = \frac{v \cdot t}{2}\) — по теории линий передач.
+- Параллельное подключение: \(Z_t = \frac{R \cdot Z_0}{R + Z_0}\), ток \(I = \frac{U}{Z_0 + Z_t}\); при \(R \to 0\) ток резко растёт. Проверено на крайних значениях \(R = 1\) Ом и \(R = 1000\) Ом.
+- Последовательное подключение: \(Z = Z_0 + R\), ток падает; при малом \(R\) изменение тока меньше 5% — в обратной связи объяснено, почему рефлектометрия нужна.
+- Суперпозиция сигналов \(S(t) = A_1\sin(2\pi f_1 t) + A_2\sin(2\pi f_2 t + \varphi)\) — по общему курсу физики.
+- Утверждение об энергонезависимости проводного радиовещания и его устойчивости к кибератакам — по новости РБК (ссылка в 1.3).
+- Триада КЦД — по ГОСТ Р 50922-2006 и учебным материалам.
+- Утверждение, что диктофон относится к акустическому каналу, а не к проводному, — по физике негласной записи речи; в интерактиве диктофон не используется, противоречие программы не воспроизводится.
+
+**Что не проверено (уточнить при финализации):**
+
+- Выходные данные учебника/пособия по ТЗИ.
+- Конкретные ГОСТы по защите информации от утечки по техническим каналам.
+- Точные значения скорости распространения \(v\) в конкретных типах линий (в модели взято приближение \(2 \cdot 10^8\) м/с, в спецификации это не фиксируется как норматив).
+
+---
+
+## 4. Журнал работы с ИИ
+
+Заполняется при разработке кода. Ожидаемые ошибки ИИ, которые нужно проверить:
+
+<table width="100%">
+<tr style="vertical-align: top; text-align: justify;">
+<td> № </td><td> Сцена </td><td> Ошибка </td><td> Что попросили </td><td>Коммит </td>
+</tr>
+<tr style="vertical-align: top; text-align: left;">
+<td colspan="5"> Что выдал ИИ </td>
+</tr>
+
+<tr style="vertical-align: top; text-align: left;">
+<td> 1 </td>
+<td> Навигация, все сцены </td>
+<td> Нет нумерации сцен. В шапке и заголовках не видно, какая сцена открыта, сложно указать, о какой именно идёт речь. </td>
+<td> Добавь нумерацию сцен в шапку и в заголовки </td>
+<td> 4.2/001_nav_1 </td>
+</tr>
+<tr style="vertical-align: top; text-align: left;">
+<td colspan="5">
+- В шапке добавлена строка «Сцена N из 9» с названием сцены.<br>
+- В заголовок каждого экрана (h2) добавлен префикс «Сцена N.».<br>
+- Сцена 1 — исключение: у неё h1 без префикса, в шапке только «Сцена 1 из 9 · Цель».
+</td>
+</tr>
+
+<tr style="vertical-align: top; text-align: left;">
+<td> 2 </td>
+<td> Сцена 4. Угроза конфиденциальности: подключение к линии </td>
+<td> Параллельное подключение нарисовано не параллельно: одна вертикальная линия вниз, устройство висит на одном проводе. Подписи удалённости и сопротивления мелкие и сливаются с линией и рефлектограммой. </td>
+<td> Сделай параллельное подключение именно параллельным; подписи сделай крупнее и не сливающимися </td>
+<td> 4.2/001_s4_1 </td>
+</tr>
+<tr style="vertical-align: top; text-align: left;">
+<td colspan="5">
+- Параллельное подключение перерисовано: два отвода от линии вниз (tx−32 и tx+32), между ними перемычка, в перемычке — устройство «ЗЛ». Видно, что съём подключён к участку линии, а не висит на одном проводе.<br>
+- Подписи вынесены в хелпер drawLabel: размер 13 px, полупрозрачная подложка под текстом, чтобы не сливались с линией и рефлектограммой.<br>
+- Удалённость подписана над точкой подключения, сопротивление — под устройством.<br>
+- Высота canvas увеличена до 340 px (300 на мобильном), чтобы подписи и рефлектограмма не наезжали друг на друга.
+</td>
+</tr>
+
+<tr style="vertical-align: top; text-align: left;">
+<td> 3 </td>
+<td> Сцена 5. Угроза целостности: подмена сигнала </td>
+<td> При перемещении ползунка «Точка врезки» итоговый сигнал не меняется. При переключении аутентификации на графике ничего не происходит. </td>
+<td> Исправь: точка врезки должна влиять на итоговый сигнал, аутентификация — на решение приёмника и на график </td>
+<td> 4.2/001_s5_1 </td>
+</tr>
+<tr style="vertical-align: top; text-align: left;">
+<td colspan="5">
+- В drawScene4 добавлена передача точки врезки: до точки врезки в строке «Сигнал в приёмнике» рисуется чистый полезный сигнал, после — сумма (или горизонтальная линия на нуле при отклонении).<br>
+- В строке «Врезка злоумышленника» сигнал появляется только правее точки врезки.<br>
+- При включённой аутентификации и отклонении после точки врезки сигнал пропадает, рядом с точкой врезки появляется надпись «ОТКЛОНЁН».<br>
+- В render() добавлен флаг rejected, который передаётся в drawScene4.<br>
+- Readout и feedback обновляются при каждом изменении ползунков и переключателя.
+</td>
+</tr>
+</table>
+
+---
+
+## 5. Критерии оценивания base (максимум 16 баллов)
+
+*Для учебного интерактива, который делает преподаватель, студенческие критерии не применяются. Раздел сохранён в шаблоне как справка: если интерактив делается студентом под руководством преподавателя, критерии применяются к студенческой части работы (код, журнал, проверка).*
+
+| № | Критерий | 0 баллов | 1 балл | 2 балла | Макс |
+|---|---|---|---|---|---|
+| 1 | **Соответствие теме** | Тема не из программы или подменена | Тема из программы, но раскрыта частично | Тема из программы, раскрыта полностью, есть отсылка к номеру темы | 2 |
+| 2 | **Корректность содержания** | Есть фактические ошибки | Ошибок нет, но нет и подтверждения источников | Все факты подтверждены ссылкой на лекцию/ГОСТ/учебник, ошибок нет | 2 |
+| 3 | **Однозначность заданий** | Есть вопросы с двумя правильными ответами или двусмысленной формулировкой | 1–2 спорных вопроса, остальные однозначны | Все вопросы однозначны | 2 |
+| 4 | **Обратная связь** | «Неверно» без пояснения или её нет | Пояснение есть, но общее | Пояснение конкретное: почему неверно и что верно | 2 |
+| 5 | **Сценарий и структура** | Меньше 3 сцен или нет логики переходов | 3+ сцены, но переходы не продуманы | 3+ сцены, логика переходов описана и работает | 2 |
+| 6 | **Журнал работы с ИИ** | Нет журнала или он пустой | Журнал есть, но без доказательств | Журнал с 2+ найденными ошибками ИИ, каждая исправлена и подтверждена коммитом | 2 |
+| 7 | **Проверка на человеке** | Не проверялось | Проверено, но замечания не исправлены | Проверено, замечания исправлены | 2 |
+| 8 | **Работоспособность** | Не открывается или не работает офлайн | Открывается, но есть сломанные сцены | Работает полностью, офлайн (PWA) | 2 |
+
+**Перевод в оценку:** 14–16 — «отлично», 11–13 — «хорошо», 8–10 — «удовлетворительно», меньше 8 — не принято.
+
+---
+
+## 6. Чек-лист приёмки
+
+**Для учебного интерактива (преподаватель):**
+
+- [ ] Цель на первом экране, конкретна: для каждой из трёх угроз назвать способ атаки и средство защиты.
+- [ ] Актуализация полная: энергонезависимость линии, физическая доступность провода на всей длине, триада КЦД.
+- [ ] Определения в блоке «ЗАПИШИ В ТЕТРАДЬ», корректны, с формулами где нужно.
+- [ ] Три угрозы показаны отдельными сценами, каждая со своей визуализацией.
+- [ ] Явно сказано: один канал, три угрозы; один прибор (рефлектометр) может обслуживать две из них.
+- [ ] Применение показано: средства защиты по триаде, связь с новостью о сохранении проводного радиовещания.
+- [ ] Тест проверяет понимание, не заучивание.
+- [ ] Обратная связь объясняет закон, а не констатирует факт.
+- [ ] Крайние случаи работают: \(R \to 0\) (КЗ), \(R\) велико (маскировка), совпадение кода (утечка регламента), обрыв в начале линии (мертва вся линейка), обрыв при резерве (обход), КЗ и помеха резервом не лечатся, рефлектометр видит и подключение, и обрыв.
+- [ ] Модель не ломается при переключении между сценами.
+- [ ] Чек-лист конспекта в конце.
+
+**Если код делал студент под руководством (дополнительно):**
+
+- [ ] Журнал верификации заполнен, 2+ ошибки ИИ, исправлены, подтверждены коммитами.
+- [ ] Проверено на одногруппнике, замечания исправлены.
+- [ ] Работает офлайн (PWA) или стабильная ссылка.
+- [ ] Автор может объяснить, какие ошибки ИИ нашёл и как исправил.