
(() => {
  "use strict";
  document.documentElement.classList.add("js");

  const $ = (selector, root = document) => root.querySelector(selector);
  const $$ = (selector, root = document) => Array.from(root.querySelectorAll(selector));

  const toast = (message) => {
    let node = $(".toast");
    if (!node) {
      node = document.createElement("div");
      node.className = "toast";
      node.setAttribute("role", "status");
      node.setAttribute("aria-live", "polite");
      document.body.appendChild(node);
    }
    node.textContent = message;
    node.classList.add("is-visible");
    window.clearTimeout(node._timer);
    node._timer = window.setTimeout(() => node.classList.remove("is-visible"), 2600);
  };

  const downloadText = (filename, text) => {
    const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
    const url = URL.createObjectURL(blob);
    const anchor = document.createElement("a");
    anchor.href = url;
    anchor.download = filename;
    document.body.appendChild(anchor);
    anchor.click();
    anchor.remove();
    URL.revokeObjectURL(url);
  };

  // Шапка и навигация
  const header = $(".site-header");
  const updateHeader = () => header?.classList.toggle("is-scrolled", window.scrollY > 10);
  updateHeader();
  window.addEventListener("scroll", updateHeader, { passive: true });

  const navToggle = $(".nav-toggle");
  const nav = $(".site-nav");
  const setNav = (open) => {
    if (!nav || !navToggle) return;
    nav.classList.toggle("is-open", open);
    navToggle.setAttribute("aria-expanded", String(open));
    document.body.classList.toggle("nav-open", open);
  };
  navToggle?.addEventListener("click", () => setNav(!nav.classList.contains("is-open")));
  $$(".site-nav a").forEach((link) => link.addEventListener("click", () => setNav(false)));
  document.addEventListener("keydown", (event) => {
    if (event.key === "Escape") setNav(false);
  });

  // Режим чтения. Предпочтение сохраняется только после осознанного нажатия.
  const themeButton = $("[data-theme-toggle]");
  const storage = {
    get(key) {
      try { return localStorage.getItem(key); } catch { return null; }
    },
    set(key, value) {
      try { localStorage.setItem(key, value); } catch { /* локальное хранилище может быть отключено */ }
    }
  };
  const storedTheme = storage.get("война-тема");
  if (storedTheme === "paper") document.documentElement.dataset.theme = "paper";
  const updateThemeLabel = () => {
    if (!themeButton) return;
    const paper = document.documentElement.dataset.theme === "paper";
    themeButton.setAttribute("aria-label", paper ? "Включить тёмный режим" : "Включить бумажный режим");
    themeButton.setAttribute("title", paper ? "Тёмный режим" : "Бумажный режим");
    const label = $("[data-theme-label]", themeButton);
    if (label) label.textContent = paper ? "Тёмный режим" : "Бумажный режим";
  };
  updateThemeLabel();
  themeButton?.addEventListener("click", () => {
    const paper = document.documentElement.dataset.theme === "paper";
    if (paper) {
      delete document.documentElement.dataset.theme;
      storage.set("война-тема", "dark");
    } else {
      document.documentElement.dataset.theme = "paper";
      storage.set("война-тема", "paper");
    }
    updateThemeLabel();
  });

  $$("[data-year]").forEach((node) => {
    node.textContent = String(new Date().getFullYear());
  });

  // Исправление позиции якоря после загрузки стилей, изображений и закреплённой навигации.
  const scrollToHash = () => {
    if (!location.hash) return;
    let id = location.hash.slice(1);
    try { id = decodeURIComponent(id); } catch { /* сохранить исходный якорь */ }
    const target = document.getElementById(id);
    if (!target) return;
    window.setTimeout(() => {
      const root = document.documentElement;
      const previous = root.style.scrollBehavior;
      root.style.scrollBehavior = "auto";
      target.scrollIntoView({ block: "start" });
      window.requestAnimationFrame(() => { root.style.scrollBehavior = previous; });
    }, 80);
  };
  window.addEventListener("load", scrollToHash);
  window.addEventListener("hashchange", scrollToHash);

  $$("[data-print]").forEach((button) => {
    button.addEventListener("click", () => window.print());
  });

  // Постепенное появление
  const reveals = $$(".reveal");
  if ("IntersectionObserver" in window && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          entry.target.classList.add("is-visible");
          observer.unobserve(entry.target);
        }
      });
    }, { threshold: 0.08 });
    reveals.forEach((node) => observer.observe(node));
  } else {
    reveals.forEach((node) => node.classList.add("is-visible"));
  }

  // Универсальные кнопки копирования
  $$("[data-copy-target]").forEach((button) => {
    button.addEventListener("click", async () => {
      const target = $(button.dataset.copyTarget);
      const value = target?.value ?? target?.textContent ?? "";
      if (!value.trim()) {
        toast("Сначала создайте текст.");
        return;
      }
      try {
        await navigator.clipboard.writeText(value);
        toast("Скопировано в буфер обмена.");
      } catch {
        target?.select?.();
        let copied=false;try{copied=document.execCommand("copy");}catch{}
        if(copied)toast("Скопировано в буфер обмена.");else{target?.focus?.();toast("Копирование не выполнено. Выделите текст и скопируйте его вручную.");}
      }
    });
  });

  // «Власть должна отвечать»: запрос четырёх прав
  const appealForm = $("#appeal-form");
  const appealOutput = $("#appeal-output");
  const buildAppeal = () => {
    if (!appealForm || !appealOutput) return "";
    const data = new FormData(appealForm);
    const date = String(data.get("date") || "[ДАТА]").trim();
    const institution = String(data.get("institution") || "[УЧРЕЖДЕНИЕ / КОМПАНИЯ]").trim();
    const decision = String(data.get("decision") || "[ОПИШИТЕ РЕШЕНИЕ]").trim();
    const deadline = String(data.get("deadline") || "[СРОК ОТВЕТА]").trim();
    const name = String(data.get("name") || "[ИМЯ]").trim();
    const contact = String(data.get("contact") || "[КОНТАКТ]").trim();
    const ref = String(data.get("reference") || "[НОМЕР ДЕЛА / АККАУНТА]").trim();

    const text = `Тема: Запрос уведомления, объяснения, пересмотра человеком и исправления

${date} ${institution} приняло или существенно повлияло на следующее решение, затронувшее меня:

${decision}

1. УВЕДОМЛЕНИЕ
Повлияли ли на это решение алгоритм, автоматизированная оценка, ранжирование, прогнозная модель, генеративная система или автоматизированный рабочий процесс? Прошу указать роль системы, ответственное учреждение, дату решения и устойчивый номер дела.

2. ОБЪЯСНЕНИЕ
Какие данные, правила, цели, существенные факторы и исходные записи повлияли на результат? Прошу предоставить понятное человеку объяснение и указать, какие сведения я могу проверить или исправить.

3. ПЕРЕСМОТР ЧЕЛОВЕКОМ
Кто является ответственным человеком, имеющим полномочия пересмотреть или отменить решение? Прошу сообщить процедуру, крайний срок и ожидаемое время ответа.

4. ИСПРАВЛЕНИЕ
Если решение или использованные данные ошибочны, какой процесс исправит исходную запись, уведомит зависимые системы, отменит решение и восстановит утраченные доступ, оплату, льготы, статус, возможность, охват, репутацию или правовое положение?

Прошу сохранить все записи, относящиеся к этому запросу, и ответить до ${deadline}.

${name}
${contact}
${ref}

Примечание: перед публичным распространением удалите личные данные, которые не нужны для доказательства произошедшего.

ВЛАСТЬ ДОЛЖНА ОТВЕЧАТЬ`;
    appealOutput.value = text;
    return text;
  };
  appealForm?.addEventListener("input", buildAppeal);
  appealForm?.addEventListener("submit", (event) => {
    event.preventDefault();
    buildAppeal();
    appealOutput?.focus();
    toast("Запрос сформирован локально. Данные никуда не отправлены.");
  });
  $("#appeal-download")?.addEventListener("click", () => {
    const text = buildAppeal();
    if (text) downloadText("запрос-власть-должна-отвечать", text);
  });
  $("#appeal-reset")?.addEventListener("click", () => {
    appealForm?.reset();
    if (appealOutput) appealOutput.value = "";
    toast("Поля очищены.");
  });
  if (appealForm && appealOutput) buildAppeal();

  // Тест репликации
  const test = $("#replication-test");
  const scoreNode = $("#replication-score");
  const resultTitle = $("#replication-title");
  const resultText = $("#replication-text");
  const calculateTest = () => {
    if (!test || !scoreNode || !resultTitle || !resultText) return;
    const groups = $$("[data-test-group]", test);
    let answered = 0;
    let score = 0;
    groups.forEach((group) => {
      const checked = $('input[type="radio"]:checked', group);
      if (checked) {
        answered += 1;
        score += Number(checked.value);
      }
    });
    const max = groups.length * 2;
    scoreNode.innerHTML = `<span>${score}</span>/${max}`;

    if (answered < groups.length) {
      resultTitle.textContent = `Ответов: ${answered} из ${groups.length}`;
      resultText.textContent = "Пройдите все пункты. «Неизвестно» — честный ответ и тоже часть аудита.";
      return;
    }
    if (score >= 17) {
      resultTitle.textContent = "Система выдерживает проверку";
      resultText.textContent = "Права на оппозицию, выход, аудит и мирную смену власти выглядят структурными. Проверяйте практику, а не только правила на бумаге.";
    } else if (score >= 11) {
      resultTitle.textContent = "Смешанный результат";
      resultText.textContent = "Свобода существует рядом с механизмами захвата. Сосредоточьтесь на пунктах «нет» и «неизвестно»: именно там временная власть обычно становится постоянной.";
    } else {
      resultTitle.textContent = "Высокий риск нового трона";
      resultText.textContent = "Система воспроизводит архитектуру, против которой выступала: закрывает выход, аудит, оппозицию или сменяемость. Победа здесь не равна освобождению.";
    }
  };
  test?.addEventListener("change", calculateTest);
  $("#replication-reset")?.addEventListener("click", () => {
    $$('input[type="radio"]', test).forEach((input) => { input.checked = false; });
    calculateTest();
  });
  calculateTest();

  // Конструктор плана круга действия
  const circleForm = $("#circle-form");
  const circleOutput = $("#circle-output");
  const buildCircle = () => {
    if (!circleForm || !circleOutput) return "";
    const data = new FormData(circleForm);
    const place = String(data.get("place") || "[МЕСТО / СООБЩЕСТВО]").trim();
    const need = String(data.get("need") || "[ОДНА КОНКРЕТНАЯ ПОТРЕБНОСТЬ]").trim();
    const project = String(data.get("project") || "[ОДИН ПРОЕКТ]").trim();
    const artifact = String(data.get("artifact") || "[ОДИН ПРОВЕРЯЕМЫЙ АРТЕФАКТ]").trim();
    const date = String(data.get("meeting") || "[ДАТА ВСТРЕЧИ]").trim();
    const measure = String(data.get("measure") || "[КАК ПОЙМЁМ, ЧТО РАБОТАЕТ]").trim();

    const text = `# ОТКРЫТЫЙ КРУГ ДЕЙСТВИЯ

Место / сообщество: ${place}
Первая встреча: ${date}

## Потребность
${need}

## Один проект
${project}

## Один артефакт
${artifact}

## Проверка результата
${measure}

## Роли на встрече
- Фасилитатор — следит за временем и равным голосом.
- Секретарь — сохраняет решения и незакрытые вопросы.
- Проверяющий — отделяет доказанное от предположенного.
- Распорядитель — делает ресурсы и расходы видимыми.
- Оппонент — проверяет план на скрытую власть и вред.

Роли вращаются. Постоянных и тайных рангов нет.

## Минимальные правила
1. Круг открыт и ненасильственен.
2. Решения, ресурсы, конфликты и результаты видимы.
3. Несогласие сохраняется; выход свободен.
4. Личные данные собираются только при необходимости.
5. Цель — не лояльность, а работающий результат.
6. Каждая встреча заканчивается следующим действием, ответственным человеком и сроком.
7. Никто не становится символом, заменяющим совесть остальных.
8. Круг отвечает за собственную власть.

ОДИН КРУГ · ОДИН ПРОЕКТ · ОДИН АРТЕФАКТ
СТРОЙ · ПРОВЕРЯЙ · УЧИ · ВОСПРОИЗВОДИ · ВОССТАНАВЛИВАЙ`;
    circleOutput.value = text;
    return text;
  };
  circleForm?.addEventListener("input", buildCircle);
  circleForm?.addEventListener("submit", (event) => {
    event.preventDefault();
    buildCircle();
    circleOutput?.focus();
    toast("План создан локально.");
  });
  $("#circle-download")?.addEventListener("click", () => {
    const text = buildCircle();
    if (text) downloadText("план-круга-действия", text);
  });
  $("#circle-reset")?.addEventListener("click", () => {
    circleForm?.reset();
    if (circleOutput) circleOutput.value = "";
    toast("Поля очищены.");
  });
  if (circleForm && circleOutput) buildCircle();

  // Фильтры и поиск архива
  const archiveSearch = $("#archive-search");
  const archiveItems = $$(".archive-item");
  const filterButtons = $$(".filter-button");
  const requestedFilter = new URLSearchParams(location.search).get("фильтр");
  let activeFilter = ["все", "книга", "основа", "стратегия", "когнитивная", "цензура"].includes(requestedFilter) ? requestedFilter : "все";
  if (activeFilter !== "все") {
    filterButtons.forEach((button) => {
      const active = button.dataset.filter === activeFilter;
      button.classList.toggle("is-active", active);
      button.setAttribute("aria-pressed", String(active));
    });
  }
  const applyArchiveFilter = () => {
    if (!archiveItems.length) return;
    const query = (archiveSearch?.value || "").trim().toLocaleLowerCase("ru");
    let visible = 0;
    archiveItems.forEach((item) => {
      const typeMatches = activeFilter === "все" || item.dataset.series === activeFilter;
      const textMatches = !query || item.textContent.toLocaleLowerCase("ru").includes(query);
      const show = typeMatches && textMatches;
      item.hidden = !show;
      if (show) visible += 1;
    });
    $(".no-results")?.classList.toggle("is-visible", visible === 0);
    const count = $("#archive-count");
    if (count) count.textContent = `${visible} из ${archiveItems.length}`;
  };
  archiveSearch?.addEventListener("input", applyArchiveFilter);
  filterButtons.forEach((button) => {
    button.addEventListener("click", () => {
      activeFilter = button.dataset.filter || "все";
      filterButtons.forEach((item) => {
        const active = item === button;
        item.classList.toggle("is-active", active);
        item.setAttribute("aria-pressed", String(active));
      });
      applyArchiveFilter();
    });
  });
  applyArchiveFilter();

  // Кнопки сохранения произвольного текста
  $$("[data-download-target]").forEach((button) => {
    button.addEventListener("click", () => {
      const target = $(button.dataset.downloadTarget);
      const text = target?.value ?? target?.textContent ?? "";
      if (!text.trim()) {
        toast("Нет текста для сохранения.");
        return;
      }
      downloadText(button.dataset.filename || "заметка-война-ком", text);
    });
  });

  // Локальная проверка целостности файлов.
  const integrityButton = $("#integrity-open");
  const integrityFile = $("#integrity-file");
  const integrityResult = $("#integrity-result");
  integrityButton?.addEventListener("click", () => integrityFile?.click());
  integrityFile?.addEventListener("change", async () => {
    const file = integrityFile.files?.[0];
    if (!file || !integrityResult) return;
    integrityResult.textContent = "Вычисляется проверочный отпечаток…";
    try {
      const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", await file.arrayBuffer()));
      let decimal = 0n;
      for (const byte of bytes) decimal = (decimal << 8n) + BigInt(byte);
      const response = await fetch("МАНИФЕСТ-ИСТОЧНИКОВ.json", { cache: "no-store" });
      if (!response.ok) throw new Error("manifest");
      const manifest = await response.json();
      const match = (manifest["файлы"] || []).find((entry) => entry["ша_256_десятичное"] === decimal.toString());
      if (match) {
        integrityResult.textContent = `Совпадение подтверждено: ${match["название"]}. Размер: ${file.size} байт.`;
      } else {
        integrityResult.textContent = "Совпадение не найдено. Файл может быть изменён, повреждён или отсутствовать в этом выпуске.";
      }
    } catch {
      integrityResult.textContent = "Проверка не выполнена. Откройте сайт через защищённое соединение или локальный веб-сервер.";
    }
  });


  // Матрица устойчивости командования.
  const leaderInputs = {
    personalization: $("#lm-personalization"),
    centralization: $("#lm-centralization"),
    depth: $("#lm-depth"),
    succession: $("#lm-succession"),
    autonomy: $("#lm-autonomy"),
    community: $("#lm-community"),
    age: $("#lm-age"),
    martyrdom: $("#lm-martyrdom")
  };
  const clamp = (value) => Math.max(0, Math.min(100, Math.round(value)));
  const setMeter = (name, value) => {
    const fill = $(`#lm-${name}`);
    const label = $(`#lm-${name}-value`);
    if (fill) fill.style.width = `${value}%`;
    if (label) label.textContent = String(value);
  };
  const calculateLeadership = () => {
    if (!leaderInputs.personalization) return;
    const v = Object.fromEntries(Object.entries(leaderInputs).map(([key, node]) => [key, Number(node?.value || 0)]));
    Object.entries(leaderInputs).forEach(([key, node]) => {
      const label = $(`#lm-${key}-value`);
      if (label && node) label.textContent = node.value;
    });
    const resilience = clamp((v.depth * 2.1 + v.succession * 1.8 + v.autonomy * 1.7 + v.age * 1.2 + v.community * 1.1 + (10 - v.personalization) * 1.1) * 1.1);
    const disruption = clamp((v.personalization * 2.5 + v.centralization * 2.2 + (10 - v.succession) * 1.8 + (10 - v.autonomy) * 1.5 + (10 - v.depth) * 1.4) * 1.06);
    const collapse = clamp((v.personalization * 2.6 + v.centralization * 1.8 + (10 - v.depth) * 2.0 + (10 - v.succession) * 2.1 + (10 - v.autonomy) * 1.4 + (10 - v.community) * .8));
    const escalation = clamp((v.martyrdom * 3.0 + v.community * 2.0 + v.age * 1.2 + v.centralization * .8 + (10 - v.succession) * .8) * 1.05);
    setMeter("resilience", resilience);
    setMeter("disruption", disruption);
    setMeter("collapse", collapse);
    setMeter("escalation", escalation);
    const verdict = $("#lm-verdict");
    const explain = $("#lm-explain");
    if (!verdict || !explain) return;
    if (collapse >= 68 && escalation >= 58) {
      verdict.textContent = "Высокий риск распада с обратной мобилизацией";
      explain.textContent = "Персональная зависимость и слабое преемство делают первый удар разрушительным, а общественная опора и символический потенциал способны превратить потерю в новый набор и месть.";
    } else if (collapse >= 68) {
      verdict.textContent = "Высокий риск организационного распада";
      explain.textContent = "Командование, преемство и автономия слишком сосредоточены в вершине. После её потери функции не имеют готового носителя.";
    } else if (resilience >= 68 && disruption < 55) {
      verdict.textContent = "Система вероятно переживёт потерю без стратегического перелома";
      explain.textContent = "Преемство, институциональная глубина и автономия штабов распределяют функции. Потеря лица не равна потере аппарата.";
    } else if (resilience >= 58 && disruption >= 55) {
      verdict.textContent = "Оперативная пауза при сохранении системы";
      explain.textContent = "Аппарат переживёт удар, но временно потеряет темп, согласование и общую интуицию. Качество второго эшелона станет решающим.";
    } else if (escalation >= 68) {
      verdict.textContent = "Низкий шанс распада, высокий шанс усиления символа";
      explain.textContent = "Организация достаточно устойчива, чтобы продолжить работу, а смерть или удаление центра может дать ей долговечный мобилизационный образ.";
    } else {
      verdict.textContent = "Смешанный результат: временный сбой и спорное преемство";
      explain.textContent = "Ни один механизм не доминирует. Итог будет зависеть от первых суток: признания преемника, связи, снабжения, поведения внешних покровителей и реакции общины.";
    }
  };
  Object.values(leaderInputs).forEach((node) => node?.addEventListener("input", calculateLeadership));
  calculateLeadership();

  // Красная группа разведывательной уверенности.
  const intelAudit = $("#intel-audit");
  const calculateIntel = () => {
    if (!intelAudit) return;
    const groups = $$('[data-intel-group]', intelAudit);
    let answered = 0;
    let score = 0;
    groups.forEach((group) => {
      const checked = $('input[type="radio"]:checked', group);
      if (checked) {
        answered += 1;
        score += Number(checked.value);
      }
    });
    const scoreNode = $("#intel-score");
    const title = $("#intel-title");
    const text = $("#intel-text");
    if (scoreNode) scoreNode.innerHTML = `<span>${score}</span>/20`;
    if (!title || !text) return;
    if (answered < groups.length) {
      title.textContent = `Ответов: ${answered} из ${groups.length}`;
      text.textContent = "Незаполненный пункт остаётся неизвестностью, а не молчаливым подтверждением основного вывода.";
      return;
    }
    if (score >= 17) {
      title.textContent = "Высокая разведывательная дисциплина";
      text.textContent = "Источник, альтернативная модель, логистика, окно и условия опровержения представлены. Решение может опираться на видимую цепь рассуждения.";
    } else if (score >= 12) {
      title.textContent = "Оценка частично защищена от политического захвата";
      text.textContent = "Сильные элементы существуют, но оставшиеся неизвестности способны изменить исход. Перед необратимым решением требуется закрыть именно эти пробелы.";
    } else if (score >= 7) {
      title.textContent = "Высокий риск априорного вывода";
      text.textContent = "Оценка зависит от предположений, незащищённых источников или непроверенной логистики. Уверенный тон здесь опаснее честной задержки.";
    } else {
      title.textContent = "Разведывательный процесс захвачен решением";
      text.textContent = "Основной вывод не прошёл независимое подтверждение, красную группу и проверку опровержением. Это политическая конструкция, а не устойчивая оценка.";
    }
  };
  intelAudit?.addEventListener("change", calculateIntel);
  $("#intel-reset")?.addEventListener("click", () => {
    $$('input[type="radio"]', intelAudit).forEach((input) => { input.checked = false; });
    calculateIntel();
  });
  calculateIntel();

  // Поиск и фильтры исторической картотеки.
  const caseSearch = $("#case-search");
  const caseAxis = $("#case-axis");
  const caseResult = $("#case-result");
  const caseConfidence = $("#case-confidence");
  const caseCards = $$(".case-card");
  const requestedAxis = new URLSearchParams(location.search).get("ось");
  if (requestedAxis && caseAxis && Array.from(caseAxis.options).some((option) => option.value === requestedAxis)) caseAxis.value = requestedAxis;
  const applyCaseFilter = () => {
    if (!caseCards.length) return;
    const query = (caseSearch?.value || "").trim().toLocaleLowerCase("ru");
    const axis = caseAxis?.value || "все";
    const result = caseResult?.value || "все";
    const confidence = caseConfidence?.value || "все";
    let visible = 0;
    caseCards.forEach((card) => {
      const show = (!query || card.textContent.toLocaleLowerCase("ru").includes(query))
        && (axis === "все" || card.dataset.caseAxis === axis)
        && (result === "все" || card.dataset.caseResult === result)
        && (confidence === "все" || card.dataset.caseConfidence === confidence);
      card.hidden = !show;
      if (show) visible += 1;
    });
    const count = $("#case-count");
    if (count) count.textContent = String(visible);
    $("#case-empty")?.classList.toggle("is-visible", visible === 0);
  };
  [caseSearch, caseAxis, caseResult, caseConfidence].forEach((node) => node?.addEventListener(node === caseSearch ? "input" : "change", applyCaseFilter));
  applyCaseFilter();


  // Локальная проверка храповика полномочий.
  const ratchetTest = $("#ratchet-test");
  const calculateRatchet = () => {
    if (!ratchetTest) return "";
    const questions = $$('[data-ratchet-question]', ratchetTest);
    let answered = 0;
    let points = 0;
    let unknown = 0;
    const critical = [];
    const lines = ["ПРОВЕРКА ХРАПОВИКА ПОЛНОМОЧИЙ", ""];
    questions.forEach((question, index) => {
      const checked = $('input[type="radio"]:checked', question);
      const text = $("legend strong", question)?.textContent.trim() || `Проверка ${index + 1}`;
      const answer = checked?.dataset.ratchetAnswer || "нет ответа";
      const value = checked ? Number(checked.value) : 3;
      if (checked) answered += 1;
      if (answer === "неизвестно" || !checked) unknown += 1;
      points += value;
      lines.push(`${index + 1}. ${text}`, `   Ответ: ${answer}.`);
      if (question.dataset.critical === "true" && (!checked || value >= 2)) critical.push(`${index + 1}. ${text} — ${answer}.`);
    });
    const risk = Math.round(points / (questions.length * 3) * 100);
    const score = $("#ratchet-score");
    if (score) score.innerHTML = `<span>${risk}</span>/100`;
    const progress = $("#ratchet-progress");
    if (progress) progress.textContent = `Ответов: ${answered} из ${questions.length} · неизвестно: ${unknown}`;
    const title = $("#ratchet-title");
    const text = $("#ratchet-text");
    if (title && text) {
      if (answered < questions.length) {
        title.textContent = "Контур не завершён";
        text.textContent = "Незаполненные пункты считаются максимальным риском до появления доказательства предохранителя.";
      } else if (risk <= 20 && critical.length === 0) {
        title.textContent = "Храповик ограничен архитектурой";
        text.textContent = "Цель, срок, минимизация, пересмотр и техническая граница подтверждены. Проверка должна повторяться после каждого расширения полномочий.";
      } else if (risk <= 45) {
        title.textContent = "Граница зависит от оператора";
        text.textContent = "Часть предохранителей существует, но скрытое расширение остаётся организационно или технически возможным.";
      } else if (risk <= 70) {
        title.textContent = "Высокая вероятность расползания";
        text.textContent = "Широкая цель, неизвестный срок, вторичное использование или слабый пересмотр превращают исключение в постоянный контур.";
      } else {
        title.textContent = "Инфраструктура готова к расширению";
        text.textContent = "Полномочие способно перейти от конкретного вреда к общему наблюдению за законной интеллектуальной жизнью без достаточного сопротивления системы.";
      }
    }
    const list = $("#ratchet-critical");
    if (list) {
      list.replaceChildren();
      const entries = critical.length ? critical : ["Критических разрывов по выбранным ответам не зафиксировано."];
      entries.forEach((entry) => { const li = document.createElement("li"); li.textContent = entry; list.appendChild(li); });
    }
    lines.push("", `ИНДЕКС РИСКА: ${risk}/100`, `ОТВЕТОВ: ${answered} ИЗ ${questions.length}`, `НЕИЗВЕСТНО: ${unknown}`, "", "КРИТИЧЕСКИЕ РАЗРЫВЫ");
    lines.push(...(critical.length ? critical.map((entry) => `- ${entry}`) : ["- Не зафиксированы."]));
    lines.push("", "ТЯЖЕСТЬ СТРАХА НЕ ДОКАЗЫВАЕТ ЭФФЕКТИВНОСТЬ МЕРЫ.", "КОНТРОЛЬ ДОЛЖЕН СТОЯТЬ У КОНКРЕТНОГО ВРЕДОНОСНОГО ДЕЙСТВИЯ, А НЕ НАД ВСЕЙ ОБЛАСТЬЮ ЧТЕНИЯ И СОМНЕНИЯ.");
    const report = lines.join("\n");
    const output = $("#ratchet-output");
    if (output) output.value = report;
    return report;
  };
  ratchetTest?.addEventListener("change", calculateRatchet);
  ratchetTest?.addEventListener("submit", (event) => { event.preventDefault(); calculateRatchet(); $("#ratchet-output")?.focus(); toast("Проверка сформирована локально."); });
  $("#ratchet-download")?.addEventListener("click", () => { const report = calculateRatchet(); if (report) downloadText("проверка-храповика.txt", report); });
  $("#ratchet-reset")?.addEventListener("click", () => { ratchetTest?.reset(); calculateRatchet(); toast("Проверка очищена."); });
  calculateRatchet();

  // Локальный аудит когнитивной свободы.
  const cognitiveAudit = $("#cognitive-audit");
  const auditAnswerLabels = { "да": "Да, проверено", "частично": "Частично", "неизвестно": "Неизвестно", "нет": "Нет" };
  const calculateCognitiveAudit = () => {
    if (!cognitiveAudit) return "";
    const questions = $$('[data-audit-question]', cognitiveAudit);
    const categories = $$('[data-audit-category]', cognitiveAudit);
    let total = 0;
    let answered = 0;
    let unknown = 0;
    let negative = 0;
    const critical = [];
    const lines = [];
    const organization = $("#audit-organization")?.value.trim() || "[ОРГАНИЗАЦИЯ НЕ УКАЗАНА]";
    const system = $("#audit-system")?.value.trim() || "[СИСТЕМА НЕ УКАЗАНА]";
    const version = $("#audit-version")?.value.trim() || "[ВЕРСИЯ НЕ УКАЗАНА]";
    const date = $("#audit-date")?.value.trim() || "[ДАТА НЕ УКАЗАНА]";
    const auditor = $("#audit-auditor")?.value.trim() || "[ПРОВЕРЯЮЩИЙ НЕ УКАЗАН]";
    lines.push("АУДИТ КОГНИТИВНОЙ СВОБОДЫ", "", `Организация: ${organization}`, `Система: ${system}`, `Версия: ${version}`, `Дата: ${date}`, `Проверяющий: ${auditor}`, "");

    categories.forEach((category, categoryIndex) => {
      const categoryQuestions = $$('[data-audit-question]', category);
      let categoryScore = 0;
      const categoryName = $("summary strong", category)?.textContent.trim() || "КАТЕГОРИЯ";
      lines.push(`${categoryIndex + 1}. ${categoryName}`);
      categoryQuestions.forEach((question, questionIndex) => {
        const checked = $('input[type="radio"]:checked', question);
        const legend = $("legend strong", question)?.textContent.trim() || "Вопрос";
        const answerType = checked?.dataset.auditAnswer || "нет ответа";
        const value = checked ? Number(checked.value) : 0;
        const answer = checked ? auditAnswerLabels[answerType] : "Нет ответа";
        if (checked) answered += 1;
        if (!checked || answerType === "неизвестно") unknown += 1;
        if (answerType === "нет") negative += 1;
        total += value;
        categoryScore += value;
        lines.push(`  ${questionIndex + 1}. ${legend}`, `     Ответ: ${answer}`);
        if (question.dataset.critical === "true" && (!checked || answerType === "неизвестно" || answerType === "нет")) {
          critical.push(`${categoryName}: ${legend} — ${answer}.`);
        }
      });
      const evidence = $(".audit-evidence textarea", category)?.value.trim();
      if (evidence) lines.push(`  Доказательства и примечания: ${evidence}`);
      else lines.push("  Доказательства и примечания: не зафиксированы.");
      const normalized = Math.round(categoryScore / (categoryQuestions.length * 4) * 100);
      const scoreOutput = $("[data-category-score]", category);
      if (scoreOutput) scoreOutput.textContent = `${normalized}/100`;
      const meter = $(`[data-audit-meter="${category.dataset.auditCategory}"]`, cognitiveAudit);
      if (meter) {
        const bar = $(".meter i", meter);
        const number = $("b", meter);
        if (bar) bar.style.width = `${normalized}%`;
        if (number) number.textContent = String(normalized);
      }
      lines.push(`  Балл категории: ${normalized}/100`, "");
    });

    const overall = Math.round(total / (questions.length * 4) * 100);
    const scoreNode = $("#cognitive-audit-score");
    if (scoreNode) scoreNode.innerHTML = `<span>${overall}</span>/100`;
    const progress = $("#cognitive-audit-progress");
    if (progress) progress.textContent = `Ответов: ${answered} из ${questions.length}`;
    const unknownNode = $("#cognitive-audit-unknown");
    if (unknownNode) unknownNode.textContent = `Неизвестно или без ответа: ${unknown} · Нет: ${negative}`;
    const title = $("#cognitive-audit-title");
    const text = $("#cognitive-audit-text");
    if (title && text) {
      if (answered < questions.length) {
        title.textContent = "Аудит не завершён";
        text.textContent = "Незаполненный пункт остаётся критическим пробелом. Предварительный средний балл не превращает неизвестность в соответствие.";
      } else if (overall >= 85 && critical.length === 0 && unknown === 0) {
        title.textContent = "Сильная архитектура когнитивной свободы";
        text.textContent = "Основные права подтверждаются поведением системы. Повторяйте аудит после каждого существенного изменения версии, политики, поставщика и цепи данных.";
      } else if (overall >= 65) {
        title.textContent = "Защита неполна и зависит от оператора";
        text.textContent = "Рабочие ограничения существуют, но остаются пробелы, через которые запрос, профиль или чрезвычайное полномочие могут стать постоянным досье.";
      } else if (overall >= 40) {
        title.textContent = "Системный риск когнитивного контроля";
        text.textContent = "Приватность, процедура и техническое разделение недостаточны. Приоритет — критические провалы, а не косметическое увеличение среднего балла.";
      } else {
        title.textContent = "Инфраструктура наблюдения преобладает над агентностью";
        text.textContent = "Система связывает интеллектуальную жизнь с профилем и последствием, не обеспечивая достаточного удаления, пересмотра, выхода и разделения власти.";
      }
    }
    const criticalList = $("#cognitive-audit-critical");
    if (criticalList) {
      criticalList.replaceChildren();
      const entries = critical.length ? critical : ["Критических пробелов по выбранным ответам не зафиксировано."];
      entries.forEach((entry) => { const li = document.createElement("li"); li.textContent = entry; criticalList.appendChild(li); });
    }
    lines.push(`ОБЩИЙ БАЛЛ: ${overall}/100`, `ОТВЕТОВ: ${answered} ИЗ ${questions.length}`, `НЕИЗВЕСТНО ИЛИ БЕЗ ОТВЕТА: ${unknown}`, `ОТВЕТОВ «НЕТ»: ${negative}`, "", "КРИТИЧЕСКИЕ ПРОБЕЛЫ");
    lines.push(...(critical.length ? critical.map((entry) => `- ${entry}`) : ["- Не зафиксированы."]));
    lines.push("", "АКСИОМЫ", "ЗАПРОС НЕ РАВЕН НАМЕРЕНИЮ.", "ВЫВОД НЕ РАВЕН ФАКТУ.", "ПРОФИЛЬ НЕ РАВЕН ЛИЧНОСТИ.", "БЕЗОПАСНОСТЬ НЕ РАВНА НАБЛЮДЕНИЮ.", "ОТКАЗ СИСТЕМЫ НЕ РАВЕН ИСТИНЕ.");
    const report = lines.join("\n");
    const output = $("#cognitive-audit-output");
    if (output) output.value = report;
    return report;
  };
  cognitiveAudit?.addEventListener("change", calculateCognitiveAudit);
  cognitiveAudit?.addEventListener("input", calculateCognitiveAudit);
  cognitiveAudit?.addEventListener("submit", (event) => { event.preventDefault(); calculateCognitiveAudit(); $("#cognitive-audit-output")?.focus(); toast("Отчёт сформирован локально. Данные никуда не отправлены."); });
  $("#cognitive-audit-download")?.addEventListener("click", () => { const report = calculateCognitiveAudit(); if (report) downloadText("аудит-когнитивной-свободы.txt", report); });
  $("#cognitive-audit-reset")?.addEventListener("click", () => { cognitiveAudit?.reset(); calculateCognitiveAudit(); toast("Аудит очищен."); });
  calculateCognitiveAudit();

  // Служебный обработчик работает только через сетевой протокол, но не при локальном просмотре файла.
  if ("serviceWorker" in navigator && location.protocol.startsWith("http")) {
    window.addEventListener("load", () => {
      navigator.serviceWorker.register("/служба.js?редакция=4.8.0", { updateViaCache: "none" }).catch(() => {
        // Офлайн-поддержка является дополнительной возможностью; сайт остаётся работоспособным без неё.
      });
    });
  }

  // КОНТУР ЦЕНЗУРЫ 4.2.0
  const censorshipTest = $("#censorship-test");
  const calculateCensorship = () => {
    if (!censorshipTest) return "";
    const questions = $$('[data-censorship-question]', censorshipTest);
    let answered = 0;
    let points = 0;
    let unknown = 0;
    const critical = [];
    const lines = ["РАДАР РАННЕГО ПРЕДУПРЕЖДЕНИЯ О ЦЕНЗУРНОМ ДРЕЙФЕ", ""];
    questions.forEach((question, index) => {
      const checked = $('input[type="radio"]:checked', question);
      const title = $("legend strong", question)?.textContent.trim() || `Рубеж ${index + 1}`;
      const answer = checked?.dataset.censorshipAnswer || "неизвестно";
      const value = checked ? Number(checked.value) : 2;
      if (checked) answered += 1; else unknown += 1;
      points += value;
      lines.push(`${index + 1}. ${title}`, `   Ответ: ${answer}.`);
      if (question.dataset.critical === "true" && value >= 1) critical.push(`${index + 1}. ${title} — ${answer}.`);
    });
    const score = $("#censorship-score");
    if (score) score.innerHTML = `<span>${points}</span>/32`;
    const title = $("#censorship-title");
    const copy = $("#censorship-text");
    if (title && copy) {
      if (answered < questions.length) {
        title.textContent = "Контур не завершён";
        copy.textContent = `Ответов: ${answered} из ${questions.length}. Неизвестно: ${unknown}. Незаполненный критический рубеж нельзя считать безопасным.`;
      } else if (points <= 7 && critical.length === 0) {
        title.textContent = "Изолированные трения";
        copy.textContent = "Отдельные ограничения существуют, но сцепление слоёв и критические точки захвата по ответам не подтверждены. Повторяйте измерение после каждого нового полномочия.";
      } else if (points <= 15) {
        title.textContent = "Селективная инфраструктура ограничения";
        copy.textContent = "Несколько слоёв уже связаны. Система способна ограничивать отдельные группы, темы или каналы без общего формального запрета.";
      } else if (points <= 23) {
        title.textContent = "Системный цензурный дрейф";
        copy.textContent = "Право, инфраструктура, идентичность или процедура соединяются. Следующее расширение может превратить выборочную меру в обычную систему управления доступом.";
      } else {
        title.textContent = "Контур цензурного государства";
        copy.textContent = "Слои образуют замкнутую архитектуру: человек определяется, канал контролируется, видимость меняется, деньги ограничиваются, память переписывается, а независимая апелляция отсутствует.";
      }
    }
    const list = $("#censorship-critical");
    if (list) {
      list.replaceChildren();
      const entries = critical.length ? critical : ["Критические рубежи по выбранным ответам не активированы."];
      entries.forEach((entry) => { const li = document.createElement("li"); li.textContent = entry; list.appendChild(li); });
    }
    lines.push("", `ИНДЕКС СЦЕПЛЕНИЯ: ${points}/32`, `ОТВЕТОВ: ${answered} ИЗ ${questions.length}`, `НЕИЗВЕСТНО: ${unknown}`, "", "КРИТИЧЕСКИЕ РУБЕЖИ");
    lines.push(...(critical.length ? critical.map((entry) => `- ${entry}`) : ["- Не активированы."]));
    lines.push("", "СРАВНИВАТЬ МЕХАНИЗМЫ, А НЕ ФЛАГИ.", "ФОРМАЛЬНОЕ НАЛИЧИЕ НЕ РАВНО ОБНАРУЖИМОСТИ.", "АРХИВ БЕЗ ВОССТАНОВЛЕНИЯ НЕ ЯВЛЯЕТСЯ СИСТЕМОЙ.");
    const report = lines.join("\n");
    const output = $("#censorship-output");
    if (output) output.value = report;
    return report;
  };
  censorshipTest?.addEventListener("change", calculateCensorship);
  censorshipTest?.addEventListener("submit", (event) => { event.preventDefault(); calculateCensorship(); $("#censorship-output")?.focus(); toast("Радар сформирован локально."); });
  $("#censorship-download")?.addEventListener("click", () => { const report = calculateCensorship(); if (report) downloadText("радар-цензурного-дрейфа.txt", report); });
  $("#censorship-reset")?.addEventListener("click", () => { censorshipTest?.reset(); calculateCensorship(); toast("Радар очищен."); });
  calculateCensorship();

  // Проверка архивной готовности.
  const archiveReadiness = $("#archive-readiness-test");
  const calculateArchiveReadiness = () => {
    if (!archiveReadiness) return "";
    const questions = $$('[data-archive-question]', archiveReadiness);
    let answered = 0;
    let points = 0;
    const gaps = [];
    const lines = ["ПРОВЕРКА АРХИВНОЙ ГОТОВНОСТИ", ""];
    questions.forEach((question, index) => {
      const checked = $('input[type="radio"]:checked', question);
      const title = $("legend strong", question)?.textContent.trim() || `Рубеж ${index + 1}`;
      const answer = checked?.dataset.archiveAnswer || "неизвестно";
      const value = checked ? Number(checked.value) : 0;
      if (checked) answered += 1;
      points += value;
      if (value < 2) gaps.push(`${index + 1}. ${title} — ${answer}.`);
      lines.push(`${index + 1}. ${title}`, `   Ответ: ${answer}.`);
    });
    const score = $("#archive-readiness-score");
    if (score) score.innerHTML = `<span>${points}</span>/24`;
    const title = $("#archive-readiness-title");
    const copy = $("#archive-readiness-text");
    if (title && copy) {
      if (answered < questions.length) {
        title.textContent = "Проверка не завершена";
        copy.textContent = `Ответов: ${answered} из ${questions.length}. Незаполненный рубеж считается отсутствующим доказательством.`;
      } else if (points >= 21) {
        title.textContent = "Архив способен пережить потерю основного узла";
        copy.textContent = "Источник, происхождение, распределение и восстановление подтверждены. Следующее требование — повторное учение и независимая сверка.";
      } else if (points >= 15) {
        title.textContent = "Архив устойчив, но зависит от нескольких незакрытых рубежей";
        copy.textContent = "Материал имеет резерв, однако отдельная учётная запись, юрисдикция, формат или хранитель всё ещё способны нарушить восстановление.";
      } else if (points >= 9) {
        title.textContent = "Существуют копии, но система сохранения не завершена";
        copy.textContent = "Потеря основного владельца или сервиса может разрушить каталог, происхождение или возможность доказать подлинность.";
      } else {
        title.textContent = "Единая точка потери";
        copy.textContent = "Текущая запись зависит от центра. Необходимо немедленно отделить источник, создать отпечаток, автономную копию, манифест и независимого хранителя.";
      }
    }
    const list = $("#archive-readiness-gaps");
    if (list) {
      list.replaceChildren();
      const entries = gaps.length ? gaps : ["Незакрытых зависимостей по выбранным ответам не осталось."];
      entries.forEach((entry) => { const li = document.createElement("li"); li.textContent = entry; list.appendChild(li); });
    }
    lines.push("", `ИНДЕКС ГОТОВНОСТИ: ${points}/24`, `ОТВЕТОВ: ${answered} ИЗ ${questions.length}`, "", "НЕЗАКРЫТЫЕ ЗАВИСИМОСТИ");
    lines.push(...(gaps.length ? gaps.map((entry) => `- ${entry}`) : ["- Не зафиксированы."]));
    lines.push("", "СОХРАНЯЙ ИСТОЧНИК. МАРКИРУЙ ПРОИЗВОДНУЮ. ПРОВЕРЯЙ ВОССТАНОВЛЕНИЕ.");
    const report = lines.join("\n");
    const output = $("#archive-readiness-output");
    if (output) output.value = report;
    return report;
  };
  archiveReadiness?.addEventListener("change", calculateArchiveReadiness);
  archiveReadiness?.addEventListener("submit", (event) => { event.preventDefault(); calculateArchiveReadiness(); $("#archive-readiness-output")?.focus(); toast("План архивной готовности сформирован локально."); });
  $("#archive-readiness-download")?.addEventListener("click", () => { const report = calculateArchiveReadiness(); if (report) downloadText("проверка-архивной-готовности.txt", report); });
  $("#archive-readiness-reset")?.addEventListener("click", () => { archiveReadiness?.reset(); calculateArchiveReadiness(); toast("Проверка очищена."); });
  calculateArchiveReadiness();

  // Поиск по документальному корпусу.
  const docsSearch = $("#docs-search");
  const docsCards = $$(".doc-card");
  const docsButtons = $$(".docs-filter-button");
  let docsFilter = "все";
  const applyDocsFilter = () => {
    if (!docsCards.length) return;
    const query = (docsSearch?.value || "").trim().toLocaleLowerCase("ru");
    let visible = 0;
    docsCards.forEach((card) => {
      const show = (docsFilter === "все" || card.dataset.docCategory === docsFilter) && (!query || card.textContent.toLocaleLowerCase("ru").includes(query));
      card.hidden = !show;
      if (show) visible += 1;
    });
    const count = $("#docs-count");
    if (count) count.textContent = String(visible);
    $("#docs-empty")?.classList.toggle("is-visible", visible === 0);
  };
  docsSearch?.addEventListener("input", applyDocsFilter);
  docsButtons.forEach((button) => button.addEventListener("click", () => {
    docsFilter = button.dataset.docFilter || "все";
    docsButtons.forEach((item) => { const active = item === button; item.classList.toggle("is-active", active); item.setAttribute("aria-pressed", String(active)); });
    applyDocsFilter();
  }));
  applyDocsFilter();

})();

// Сохранённая страница не выдаётся за действующее сетевое свидетельство.
if ('serviceWorker' in navigator) navigator.serviceWorker.addEventListener('message', event => {
 if (event.data?.вид !== 'сохранённая-копия') return;
 let note=document.getElementById('граница-сохранённой-копии');
 if(!note){note=document.createElement('p');note.id='граница-сохранённой-копии';note.className='контур-уведомление';note.setAttribute('role','status');document.body.prepend(note);}
 note.textContent='Показана сохранённая копия '+event.data.редакция+'. Актуальная сетевая выдача не подтверждена.';
});

// Отказ сохранения не выдаётся за наличие резервной копии.
if ('serviceWorker' in navigator) navigator.serviceWorker.addEventListener('message', event => {
 if (event.data?.вид !== 'ошибка-сохранения-копии') return;
 let note=document.getElementById('ошибка-сохранения-копии');
 if(!note){note=document.createElement('p');note.id='ошибка-сохранения-копии';note.className='контур-уведомление';note.setAttribute('role','status');document.body.prepend(note);}
 note.textContent='Хранилище сохранённых копий недоступно или отклонило операцию. Наличие резервной копии не подтверждено. Эта ошибка сама по себе не означает отказа сетевой выдачи.';
});
