MediaWiki:Common.js: различия между версиями
Страница интерфейса MediaWiki
Дополнительные действия
Нет описания правки Метка: отменено |
Нет описания правки Метка: отменено |
||
| Строка 1: | Строка 1: | ||
(function() { | (function () { | ||
"use strict"; | |||
window.MediaWikiTooltips = { | window.MediaWikiTooltips = { | ||
loaded: false, | loaded: false, | ||
loading: false, | |||
callbacks: [], | callbacks: [], | ||
load: function(callback) { | load: function (callback) { | ||
if (this.loaded && window.tippy) { | if (this.loaded && window.tippy) { | ||
callback(); | callback(); | ||
return; | return; | ||
} | } | ||
this.callbacks.push(callback); | this.callbacks.push(callback); | ||
if (this.loading) return; | if (this.loading) return; | ||
this.loading = true; | this.loading = true; | ||
var self = this; | var self = this; | ||
var | var cdns = [ | ||
{ base: "https://unpkg.com", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" }, | |||
{ base: "https://cdn.jsdelivr.net/npm", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" } | |||
]; | |||
var cdnIndex = 0; | |||
var tryFallback = function (which) { | |||
self.loading = false; | |||
console.error( | |||
"[MediaWikiTooltips] Не удалось загрузить " + which + ". " + | |||
"Возможные причины: CSP вики блокирует внешние скрипты, нет сети, или CDN недоступен." | |||
); | |||
self.callbacks = []; | |||
}; | |||
var runCallbacks = function () { | |||
self.loaded = true; | |||
self.loading = false; | |||
self.callbacks.forEach(function (cb) { | |||
try { cb(); } catch (e) { console.error("[MediaWikiTooltips] callback error:", e); } | |||
}); | |||
self.callbacks = []; | |||
}; | |||
function loadTippyAndCss() { | |||
var cdn = cdns[cdnIndex]; | |||
var tippyScript = document.createElement("script"); | |||
tippyScript.src = cdn.base + cdn.tippy; | |||
tippyScript.onerror = function () { tryFallback("Tippy.js"); }; | |||
tippyScript.onload = function () { | |||
if (!window.tippy) { | |||
tryFallback("Tippy.js (не в глобальной области)"); | |||
return; | |||
} | |||
var tippyCSS = document.createElement("link"); | |||
tippyCSS.rel = "stylesheet"; | |||
tippyCSS.href = cdn.base + cdn.css; | |||
tippyCSS.onerror = function () { console.warn("[MediaWikiTooltips] Стили Tippy не загрузились."); }; | |||
document.head.appendChild(tippyCSS); | |||
console.log("[MediaWikiTooltips] Tippy.js загружен с", cdn.base); | |||
runCallbacks(); | |||
}; | }; | ||
document.head.appendChild(tippyScript); | document.head.appendChild(tippyScript); | ||
} | } | ||
function loadPopper() { | |||
var cdn = cdns[cdnIndex]; | |||
var popperScript = document.createElement("script"); | |||
popperScript.src = cdn.base + cdn.popper; | |||
popperScript.onerror = function () { | |||
} | if (cdnIndex + 1 < cdns.length) { | ||
cdnIndex++; | |||
console.warn("[MediaWikiTooltips] Повтор с CDN:", cdns[cdnIndex].base); | |||
loadPopper(); | |||
} else { | |||
tryFallback("Popper.js"); | |||
} | |||
}; | |||
popperScript.onload = loadTippyAndCss; | |||
document.head.appendChild(popperScript); | |||
} | |||
loadPopper(); | |||
}, | |||
}; | }; | ||
var SidebarModule = { | var SidebarModule = { | ||
init: function() { | init: function () { | ||
var buttons = document.querySelectorAll( | var buttons = document.querySelectorAll(".боковая-панель-кнопка"); | ||
var sections = document.querySelectorAll( | var sections = document.querySelectorAll(".боковая-панель-раздел"); | ||
if (buttons.length === 0) return; | if (buttons.length === 0) return; | ||
buttons.forEach(function(button) { | buttons.forEach(function (button) { | ||
button.addEventListener( | button.addEventListener("click", function () { | ||
var targetId = this.dataset.target; | var targetId = this.dataset.target; | ||
buttons.forEach(function(btn) { btn.classList.remove( | buttons.forEach(function (btn) { | ||
this.classList.add( | btn.classList.remove("active"); | ||
}); | |||
sections.forEach(function(section) { section.classList.remove( | this.classList.add("active"); | ||
sections.forEach(function (section) { | |||
section.classList.remove("default"); | |||
}); | |||
var targetSection = document.getElementById(targetId); | var targetSection = document.getElementById(targetId); | ||
if (targetSection) { | if (targetSection) { | ||
targetSection.classList.add( | targetSection.classList.add("default"); | ||
} | } | ||
}); | }); | ||
}); | }); | ||
buttons[0].click(); | buttons[0].click(); | ||
} | }, | ||
}; | }; | ||
var AccessTooltipsModule = { | var AccessTooltipsModule = { | ||
init: function() { | init: function () { | ||
var containers = document.querySelectorAll( | var containers = document.querySelectorAll(".допуск-контейнер"); | ||
if (containers.length === 0) return; | if (containers.length === 0) return; | ||
MediaWikiTooltips.load(function() { | MediaWikiTooltips.load(function () { | ||
containers.forEach(function(container) { | containers.forEach(function (container) { | ||
var contentElement = container.querySelector( | var contentElement = | ||
container.querySelector(".допуск-подсказка"); | |||
if (!contentElement) return; | if (!contentElement) return; | ||
var content = contentElement.innerHTML; | var content = contentElement.innerHTML; | ||
tippy(container, { | tippy(container, { | ||
| Строка 81: | Строка 131: | ||
allowHTML: true, | allowHTML: true, | ||
interactive: true, | interactive: true, | ||
placement: | placement: "auto", | ||
maxWidth: 500, | maxWidth: 500, | ||
theme: | theme: "dark", | ||
arrow: true, | arrow: true, | ||
duration: [200, 200], | duration: [200, 200], | ||
| Строка 89: | Строка 139: | ||
modifiers: [ | modifiers: [ | ||
{ | { | ||
name: | name: "preventOverflow", | ||
options: { padding: 8 } | options: { padding: 8 }, | ||
}, | }, | ||
{ | { | ||
name: | name: "flip", | ||
options: { | options: { | ||
fallbackPlacements: [ | fallbackPlacements: [ | ||
} | "top", | ||
} | "bottom", | ||
] | "right", | ||
} | "left", | ||
], | |||
}, | |||
}, | |||
], | |||
}, | |||
}); | }); | ||
}); | }); | ||
}); | }); | ||
} | }, | ||
}; | }; | ||
function run() { | var LawTooltipsModule = { | ||
init: function () { | |||
console.log("[LawTooltips] init()"); | |||
var tableCells = document.querySelectorAll( | |||
".law-tooltips td, .law-tooltips th", | |||
); | |||
console.log( | |||
return | "[LawTooltips] .law-tooltips ячеек:", | ||
tableCells.length, | |||
); | |||
if (tableCells.length === 0) { | |||
console.warn("[LawTooltips] Нет ячеек .law-tooltips — выход"); | |||
return; | |||
} | |||
function run() { | |||
console.log("[LawTooltips] run() — извлечение данных..."); | |||
var lawData = LawTooltipsModule.extractLawData(); | |||
var count = Object.keys(lawData).length; | |||
console.log( | |||
"[LawTooltips] Загружено записей законов:", | |||
count, | |||
count | |||
? Object.keys(lawData).slice(0, 10).join(", ") + | |||
(count > 10 ? "..." : "") | |||
: "", | |||
); | |||
if (count > 0) { | |||
LawTooltipsModule.initCellTooltips(tableCells, lawData); | |||
return true; | |||
} | |||
return false; | |||
} | } | ||
if (typeof MediaWikiTooltips === "undefined") { | |||
console.error( | |||
"[LawTooltips] MediaWikiTooltips не найден — тултипы не загрузятся", | |||
); | |||
run(); | |||
return; | return; | ||
} | } | ||
MediaWikiTooltips.load(function () { | |||
console.log("[LawTooltips] MediaWikiTooltips.load вызван"); | |||
if (run()) { | if (run()) { | ||
console.log( | console.log("[LawTooltips] Инициализация с первой попытки"); | ||
return; | return; | ||
} | } | ||
setTimeout(function() { | setTimeout(function () { | ||
if (run()) console.log( | if (run()) { | ||
console.log("[LawTooltips] Инициализация после 300ms"); | |||
return; | |||
} | |||
}); | setTimeout(function () { | ||
if (run()) | |||
console.log( | |||
"[LawTooltips] Инициализация после 800ms", | |||
); | |||
else | |||
console.warn( | |||
"[LawTooltips] Данные законов не найдены после всех попыток", | |||
); | |||
}, 500); | |||
}, 300); | |||
}); | |||
}, | |||
extractLawData: function () { | |||
var lawData = {}; | |||
var containers = document.querySelectorAll(".mw-collapsible"); | |||
console.log( | |||
"[LawTooltips] extractLawData: коллапсибл-блоков:", | |||
containers.length, | |||
); | |||
containers.forEach(function (block) { | |||
var content = block.querySelector(".mw-collapsible-content"); | |||
if (content && block.classList.contains("mw-collapsed")) { | |||
block.classList.remove("mw-collapsed"); | |||
var toggle = content.querySelector( | |||
".mw-collapsible-toggle", | |||
); | |||
if (toggle) toggle.setAttribute("aria-expanded", "true"); | |||
} | |||
}); | |||
var detailTables = document.querySelectorAll( | |||
".mw-collapsible-content table", | |||
); | |||
console.log( | |||
"[LawTooltips] extractLawData: таблиц в .mw-collapsible-content:", | |||
detailTables.length, | |||
); | |||
detailTables.forEach(function (table, tableIndex) { | |||
if (table.classList.contains("law-tooltips")) { | |||
console.log( | |||
"[LawTooltips] extractLawData: таблица", | |||
tableIndex, | |||
"— пропуск (law-tooltips)", | |||
); | |||
return; | |||
} | |||
var rows = table.querySelectorAll("tr"); | |||
rows.forEach(function (row) { | |||
var cells = row.querySelectorAll("td"); | |||
if (cells.length < 3) return; | |||
var crimeCell = cells[0]; | |||
var descriptionCell = cells[1]; | |||
var exampleCell = cells[2]; | |||
var anchor = crimeCell.querySelector("[id]"); | |||
if (!anchor) return; | |||
var lawCode = anchor.id; | |||
var titleElement = crimeCell.querySelector("b"); | |||
if (!titleElement || !lawCode.match(/^\d{3}$/)) return; | |||
var titleText = titleElement.textContent.trim(); | |||
var lawTitle = titleText | |||
.replace(/^\d{3}\s*/, "") | |||
.replace(/^\d{3}/, "") | |||
.trim(); | |||
var description = descriptionCell.innerHTML.trim(); | |||
var examples = []; | |||
var exampleItems = exampleCell.querySelectorAll("li"); | |||
exampleItems.forEach(function (item) { | |||
var html = item.innerHTML | |||
.trim() | |||
.replace(/<br\s*\/?>/gi, ""); | |||
if (html) examples.push(html); | |||
}); | |||
lawData[lawCode] = { | |||
title: lawTitle, | |||
description: description, | |||
examples: examples, | |||
}; | |||
}); | }); | ||
}); | }); | ||
console.log( | |||
"[LawTooltips] extractLawData: итого кодов:", | |||
Object.keys(lawData).length, | |||
); | |||
return lawData; | |||
}, | |||
createTooltipContent: function (lawCode, lawData) { | |||
var data = lawData[lawCode]; | |||
if (!data) return null; | |||
var html = '<div class="law-tooltip">'; | |||
html += | |||
'<h4 class="law-tooltip-title">' + | |||
lawCode + | |||
" - " + | |||
data.title + | |||
"</h4>"; | |||
html += | |||
'<p class="law-tooltip-description">' + | |||
data.description + | |||
"</p>"; | |||
if (data.examples && data.examples.length > 0) { | |||
html += '<div class="law-tooltip-examples">'; | |||
html += "<strong>Примеры:</strong><ul>"; | |||
data.examples.slice(0, 5).forEach(function (example) { | |||
html += "<li>" + example + "</li>"; | |||
}); | |||
html += "</ul></div>"; | |||
} | |||
html += "</div>"; | |||
return html; | |||
}, | |||
initCellTooltips: function (tableCells, lawData) { | |||
var withLink = 0, | |||
withMatch = 0, | |||
withContent = 0, | |||
tooltipsCreated = 0; | |||
tableCells.forEach(function (cell) { | |||
var link = cell.querySelector('a[href*="#"]'); | |||
if (!link) return; | |||
withLink++; | |||
var href = link.getAttribute("href"); | |||
var match = href.match(/#(\d{3})/); | |||
if (!match) return; | |||
withMatch++; | |||
var lawCode = match[1]; | |||
var tooltipContent = LawTooltipsModule.createTooltipContent( | |||
lawCode, | |||
lawData, | |||
); | |||
if (!tooltipContent) return; | |||
withContent++; | |||
cell.classList.add("law-cell-with-tooltip"); | |||
tooltipsCreated++; | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
window.location.hash = lawCode; | |||
}); | |||
if (typeof tippy === "undefined") { | |||
console.error( | |||
"[LawTooltips] tippy не найден — подключи библиотеку Tippy.js", | |||
); | |||
return; | |||
} | } | ||
tippy(cell, { | |||
content: tooltipContent, | |||
allowHTML: true, | |||
interactive: true, | |||
placement: "top", | |||
maxWidth: 400, | |||
theme: "law-theme", | |||
arrow: true, | |||
duration: [200, 150], | |||
delay: [0, 50], | |||
appendTo: document.body, | |||
boundary: "viewport", | |||
popperOptions: { | |||
strategy: "fixed", | |||
modifiers: [ | |||
{ | |||
name: "preventOverflow", | |||
options: { | |||
boundary: "viewport", | |||
padding: 20, | |||
altAxis: true, | |||
altBoundary: true, | |||
}, | |||
}, | |||
{ | |||
name: "flip", | |||
options: { | |||
fallbackPlacements: [ | |||
"bottom", | |||
"left", | |||
"right", | |||
], | |||
}, | |||
}, | |||
{ | |||
name: "computeStyles", | |||
options: { adaptive: false }, | |||
}, | |||
], | |||
}, | |||
onShow: function (instance) { | |||
document | |||
.querySelectorAll("[data-tippy-root]") | |||
.forEach(function (tooltip) { | |||
if ( | |||
tooltip._tippy && | |||
tooltip._tippy !== instance | |||
) { | |||
tooltip._tippy.hide(); | |||
} | |||
}); | |||
}, | |||
}); | |||
}); | }); | ||
console.log( | |||
"[LawTooltips] initCellTooltips: ячеек с ссылкой", | |||
withLink, | |||
}; | "| с #XXX", | ||
withMatch, | |||
"| с данными", | |||
withContent, | |||
"| тултипов создано", | |||
tooltipsCreated, | |||
); | |||
}, | |||
}; | |||
var DataTooltipsModule = { | var DataTooltipsModule = { | ||
init: function() { | init: function () { | ||
var elements = document.querySelectorAll( | var elements = document.querySelectorAll( | ||
".data-tooltip[data-text]", | |||
); | |||
if (elements.length === 0) return; | if (elements.length === 0) return; | ||
MediaWikiTooltips.load(function() { | MediaWikiTooltips.load(function () { | ||
elements.forEach(function(element) { | elements.forEach(function (element) { | ||
var tooltipText = element.getAttribute( | var tooltipText = element.getAttribute("data-text"); | ||
if (!tooltipText || !tooltipText.trim()) return; | if (!tooltipText || !tooltipText.trim()) return; | ||
tippy(element, { | tippy(element, { | ||
content: tooltipText, | content: tooltipText, | ||
allowHTML: true, | allowHTML: true, | ||
interactive: false, | interactive: false, | ||
placement: | placement: "top", | ||
maxWidth: 300, | maxWidth: 300, | ||
theme: | theme: "data-tooltip-theme", | ||
arrow: true, | arrow: true, | ||
duration: [200, 150], | duration: [200, 150], | ||
delay: [0, 50] | delay: [0, 50], | ||
}); | }); | ||
}); | }); | ||
}); | }); | ||
var observer = new MutationObserver(function(mutations) { | var observer = new MutationObserver(function (mutations) { | ||
var shouldReinit = false; | var shouldReinit = false; | ||
mutations.forEach(function(mutation) { | mutations.forEach(function (mutation) { | ||
mutation.addedNodes.forEach(function(node) { | mutation.addedNodes.forEach(function (node) { | ||
if (node.nodeType === 1) { | if (node.nodeType === 1) { | ||
if (node.classList && node.classList.contains( | if ( | ||
node.classList && | |||
node.classList.contains("data-tooltip") && | |||
node.hasAttribute("data-text") | |||
) { | |||
shouldReinit = true; | shouldReinit = true; | ||
} else if (node.querySelectorAll) { | } else if (node.querySelectorAll) { | ||
var newElements = node.querySelectorAll( | var newElements = node.querySelectorAll( | ||
".data-tooltip[data-text]", | |||
); | |||
if (newElements.length > 0) { | if (newElements.length > 0) { | ||
shouldReinit = true; | shouldReinit = true; | ||
| Строка 353: | Строка 490: | ||
}); | }); | ||
}); | }); | ||
if (shouldReinit) { | if (shouldReinit) { | ||
setTimeout(function() { DataTooltipsModule.init(); }, 100); | setTimeout(function () { | ||
DataTooltipsModule.init(); | |||
}, 100); | |||
} | } | ||
}); | }); | ||
observer.observe(document.body, { | observer.observe(document.body, { | ||
childList: true, | childList: true, | ||
subtree: true | subtree: true, | ||
}); | }); | ||
} | }, | ||
}; | }; | ||
var CopyTextModule = { | |||
init: function () { | |||
CopyTextModule.initDirectElements(); | |||
CopyTextModule.initAutoContainers(); | |||
}, | |||
initDirectElements: function () { | |||
var elements = document.querySelectorAll(".copyable-text"); | |||
if (elements.length === 0) return; | |||
elements.forEach(function (element) { | |||
CopyTextModule.cleanPreContent(element); | |||
CopyTextModule.setupElement(element); | |||
}); | |||
}, | |||
initAutoContainers: function () { | |||
var containers = document.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
if (containers.length === 0) return; | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre(container); | |||
}); | |||
// Наблюдатель за динамически появляющимися элементами | |||
var observer = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (mutation) { | |||
mutation.addedNodes.forEach(function (node) { | |||
if (node.nodeType === 1) { | |||
// Проверяем сам элемент | |||
if (node.tagName === "PRE") { | |||
var container = node.closest( | |||
".copyable-pre-container", | |||
); | |||
if ( | |||
container && | |||
!node.classList.contains("copyable-text") | |||
) { | |||
node.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(node); | |||
CopyTextModule.setupElement(node); | |||
} | |||
} | |||
// Проверяем дочерние элементы | |||
else if (node.querySelectorAll) { | |||
var containers = node.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre( | |||
container, | |||
); | |||
}); | |||
// Также проверяем pre внутри уже существующих контейнеров | |||
var preElements = node.querySelectorAll( | |||
".copyable-pre-container pre", | |||
); | |||
preElements.forEach(function (pre) { | |||
if ( | |||
!pre.classList.contains("copyable-text") | |||
) { | |||
pre.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(pre); | |||
CopyTextModule.setupElement(pre); | |||
} | |||
}); | |||
} | |||
} | |||
}); | |||
// Обрабатываем изменения атрибутов (например, style="display: none" -> "") | |||
if ( | |||
mutation.type === "attributes" && | |||
mutation.attributeName === "style" | |||
) { | |||
var target = mutation.target; | |||
if (target.style.display !== "none") { | |||
var containers = target.querySelectorAll( | |||
".copyable-pre-container", | |||
); | |||
containers.forEach(function (container) { | |||
CopyTextModule.processContainerPre(container); | |||
}); | |||
} | |||
} | |||
}); | |||
}); | |||
observer.observe(document.body, { | |||
childList: true, | |||
subtree: true, | |||
attributes: true, | |||
attributeFilter: ["style"], | |||
}); | |||
}, | |||
processContainerPre: function (container) { | |||
var preElements = container.querySelectorAll("pre"); | |||
preElements.forEach(function (pre) { | |||
if (!pre.classList.contains("copyable-text")) { | |||
pre.classList.add("copyable-text"); | |||
CopyTextModule.cleanPreContent(pre); | |||
CopyTextModule.setupElement(pre); | |||
} | |||
}); | |||
}, | |||
cleanPreContent: function (preElement) { | |||
var text = preElement.textContent || preElement.innerText; | |||
var cleanedText = text | |||
.split("\n") | |||
.map(function (line) { | |||
return line.replace(/^\s*\|\s*/, ""); | |||
}) | |||
.join("\n"); | |||
if (cleanedText !== text) { | |||
preElement.textContent = cleanedText; | |||
} | |||
}, | |||
setupElement: function (element) { | |||
element.style.cursor = "pointer"; | |||
element.addEventListener("click", CopyTextModule.handleClick); | |||
var isIOS = | |||
/iPad|iPhone|iPod/.test(navigator.userAgent) && | |||
!window.MSStream; | |||
if (isIOS) { | |||
element.style.webkitUserSelect = "text"; | |||
element.style.userSelect = "text"; | |||
} | |||
}, | |||
handleClick: function (e) { | |||
e.preventDefault(); | |||
var element = this; | |||
var textToCopy = element.textContent; | |||
if (navigator.clipboard && window.isSecureContext) { | |||
navigator.clipboard | |||
.writeText(textToCopy) | |||
'<div class="violations-list"></div>' + | '<div class="violations-list"></div>' + | ||
"</div>" + | |||
'<div class="calculation-result" style="display: none;"></div>' + | |||
'<div class="calculator-actions">' + | |||
'<button class="clear-btn" style="margin-right: 10px;">Очистить</button>' + | '<button class="clear-btn" style="margin-right: 10px;">Очистить</button>' + | ||
'<button class="calculate-btn">Рассчитать</button>' + | '<button class="calculate-btn">Рассчитать</button>' + | ||
"</div>"; | |||
// Обработчики событий | |||
calculatorInterface | |||
.querySelector(".clear-btn") | |||
.addEventListener("click", function () { | |||
LawCalculatorModule.clearSelection(); | |||
}); | |||
calculatorInterface | |||
.querySelector(".calculate-btn") | |||
.addEventListener("click", function () { | |||
LawCalculatorModule.calculateSentence(); | |||
}); | |||
return calculatorInterface; | |||
}, | |||
makeTableInteractive: function (table) { | |||
var cells = table.querySelectorAll("td"); | |||
cells.forEach( | |||
function (cell) { | |||
var link = cell.querySelector('a[href*="#"]'); | |||
if (!link) return; | |||
var href = link.getAttribute("href"); | |||
var match = href.match(/#(\d{3})/); | |||
if (!match) return; | |||
var lawCode = match[1]; | |||
if (!this.lawData[lawCode]) return; | |||
cell.classList.add("law-cell-calculator"); | |||
cell.dataset.lawCode = lawCode; | |||
// Сохраняем оригинальный обработчик клика | |||
var originalClickHandler = cell.onclick; | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
LawCalculatorModule.toggleViolation(lawCode, cell); | |||
}); | |||
}.bind(this), | |||
); | |||
}, | |||
makeTableNormal: function (table) { | |||
var cells = table.querySelectorAll(".law-cell-calculator"); | |||
cells.forEach(function (cell) { | |||
cell.classList.remove("law-cell-calculator", "selected"); | |||
// Восстанавливаем оригинальную функциональность навигации | |||
var lawCode = cell.dataset.lawCode; | |||
if (lawCode) { | |||
cell.addEventListener("click", function (e) { | |||
e.preventDefault(); | |||
window.location.hash = lawCode; | |||
}); | |||
} | |||
}); | |||
}, | |||
toggleViolation: function (lawCode, cell) { | |||
var index = this.selectedViolations.indexOf(lawCode); | |||
if (index > -1) { | |||
// Убираем нарушение | |||
this.selectedViolations.splice(index, 1); | |||
cell.classList.remove("selected"); | |||
} else { | |||
// Добавляем нарушение | |||
this.selectedViolations.push(lawCode); | |||
cell.classList.add("selected"); | |||
} | } | ||
this.updateViolationsList(); | |||
}, | |||
updateViolationsList: function () { | |||
var violationsList = document.querySelector(".violations-list"); | |||
if (!violationsList) return; | |||
violationsList.innerHTML = ""; | |||
this.selectedViolations.forEach( | |||
function (lawCode) { | |||
var law = this.lawData[lawCode]; | |||
if (!law) return; | |||
var item = document.createElement("div"); | |||
item.className = "violation-item"; | |||
item.innerHTML = | |||
lawCode + | |||
" - " + | |||
law.title + | |||
'<span class="remove-btn" data-law-code="' + | |||
lawCode + | |||
'">×</span>'; | |||
item.querySelector(".remove-btn").addEventListener( | |||
"click", | |||
function () { | |||
LawCalculatorModule.removeViolation(lawCode); | |||
}, | |||
); | |||
violationsList.appendChild(item); | |||
}.bind(this), | |||
); | |||
}, | |||
removeViolation: function (lawCode) { | |||
var index = this.selectedViolations.indexOf(lawCode); | |||
if (index > -1) { | |||
this.selectedViolations.splice(index, 1); | |||
// Убираем выделение с ячейки | |||
var cell = document.querySelector( | |||
'[data-law-code="' + lawCode + '"]', | |||
); | |||
if (cell) { | |||
cell.classList.remove("selected"); | |||
} | |||
this.updateViolationsList(); | |||
} | |||
}, | |||
clearSelection: function () { | |||
this.selectedViolations = []; | |||
// Убираем выделение со всех ячеек | |||
var selectedCells = document.querySelectorAll( | |||
".law-cell-calculator.selected", | |||
); | |||
selectedCells.forEach(function (cell) { | |||
cell.classList.remove("selected"); | |||
}); | |||
this.updateViolationsList(); | |||
this.hideResult(); | |||
}, | |||
calculateSentence: function () { | |||
if (this.selectedViolations.length === 0) { | |||
alert("Выберите хотя бы одно нарушение"); | |||
return; | |||
} | |||
var result = this.calculateSentenceLogic(this.selectedViolations); | |||
this.showResult(result); | |||
}, | |||
calculateSentenceLogic: function (violations) { | |||
// Группируем нарушения по категориям | |||
var categories = {}; | |||
violations.forEach( | |||
function (lawCode) { | |||
var law = this.lawData[lawCode]; | |||
if (!law || !law.category) return; | |||
// Используем номер статьи как ключ категории | |||
var categoryKey = this.getCategoryKeyByLawCode(lawCode); | |||
if (!categories[categoryKey]) { | |||
categories[categoryKey] = []; | |||
} | |||
categories[categoryKey].push(law); | |||
}.bind(this), | |||
); | |||
var totalMinTime = 0; | |||
var totalMaxTime = 0; | |||
var totalFine = 0; | |||
var hasCritical = false; | |||
var resultText = ""; | |||
// Обрабатываем каждую категорию | |||
Object.keys(categories).forEach( | |||
function (categoryKey) { | |||
var laws = categories[categoryKey]; | |||
var category = laws[0].category; | |||
var categoryName = this.getCategoryName(categoryKey); | |||
if (category.isCritical) { | |||
hasCritical = true; | |||
resultText += | |||
"• " + | |||
categoryName + | |||
": " + | |||
(category.punishment || "Перевод в Д-класс/казнь") + | |||
"\n"; | |||
} else { | |||
// Для одной категории берем самую тяжкую статью | |||
var maxLaw = laws.reduce(function (max, current) { | |||
var currentCode = parseInt(lawCode); | |||
var maxCode = parseInt(max.title.match(/\d{3}/)[0]); | |||
return currentCode > maxCode ? current : max; | |||
}); | |||
totalMinTime += category.minTime || 0; | |||
totalMaxTime += category.maxTime || 0; | |||
totalFine += category.fine || 0; | |||
var punishmentText = ""; | |||
if (category.punishment) { | |||
punishmentText = category.punishment; | |||
} else if (category.minTime !== undefined) { | |||
punishmentText = | |||
category.minTime === category.maxTime | |||
? category.minTime + " минут" | |||
: category.minTime + | |||
"-" + | |||
category.maxTime + | |||
" минут"; | |||
} | |||
resultText += | |||
"• " + | |||
categoryName + | |||
": " + | |||
punishmentText + | |||
" (статья " + | |||
lawCode + | |||
")\n"; | |||
} | |||
}.bind(this), | |||
); | |||
if (hasCritical) { | |||
return { | |||
isCritical: true, | |||
text: | |||
"КРИТИЧЕСКИЕ НАРУШЕНИЯ\n" + | |||
resultText + | |||
"\nПриговор: Перевод в Д-класс или казнь", | |||
}; | |||
} else if (this.pageType === "d-class") { | |||
// Для Д-класса показываем штрафы и время отдельно | |||
var summaryText = ""; | |||
if (totalFine > 0) { | if (totalFine > 0) { | ||
summaryText += | summaryText += "Общий штраф: " + totalFine + " кредитов\n"; | ||
} | } | ||
if (totalMinTime > 0 || totalMaxTime > 0) { | if (totalMinTime > 0 || totalMaxTime > 0) { | ||
summaryText += | summaryText += | ||
"Общее время: " + | |||
this.formatTime(totalMinTime, totalMaxTime) + | |||
"\n"; | |||
} | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ НАКАЗАНИЙ Д-КЛАССА\n" + | |||
resultText + | |||
"\n" + | |||
summaryText, | |||
}; | |||
} else if (totalMinTime > 0 || totalMaxTime > 0) { | |||
var timeText = this.formatTime(totalMinTime, totalMaxTime); | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ СРОКА ЗАКЛЮЧЕНИЯ\n" + | |||
resultText + | |||
"\nОбщий срок: " + | |||
timeText, | |||
}; | |||
} else { | |||
return { | |||
isCritical: false, | |||
text: | |||
"РАСЧЕТ НАКАЗАНИЙ\n" + | |||
resultText + | |||
"\nНе удалось определить наказания", | |||
}; | |||
} | |||
}, | |||
getCategoryKeyByLawCode: function (lawCode) { | |||
var code = parseInt(lawCode); | |||
if (code >= 100 && code <= 109) return "100-109"; | |||
else if (code >= 200 && code <= 211) return "200-211"; | |||
else if (code >= 300 && code <= 311) return "300-311"; | |||
else if (code >= 400 && code <= 411) return "400-411"; | |||
else if (code >= 500 && code <= 511) return "500-511"; | |||
return "unknown"; | |||
}, | |||
getCategoryName: function (categoryKey) { | |||
var names = { | |||
"100-109": "Лёгкие нарушения", | |||
"200-211": "Средние нарушения", | |||
"300-311": "Тяжкие нарушения", | |||
"400-411": "Особо тяжкие нарушения", | |||
"500-511": "Критические нарушения", | |||
}; | |||
return names[categoryKey] || "Неизвестная категория"; | |||
}, | |||
formatTime: function (minTime, maxTime) { | |||
if (minTime === maxTime) { | |||
return this.formatMinutes(minTime); | |||
} else { | |||
return ( | |||
this.formatMinutes(minTime) + | |||
" - " + | |||
this.formatMinutes(maxTime) | |||
); | |||
} | |||
}, | |||
formatMinutes: function (minutes) { | |||
if (minutes < 60) { | |||
return minutes + " минут"; | |||
} else { | |||
var hours = Math.floor(minutes / 60); | |||
var remainingMinutes = minutes % 60; | |||
var result = hours + " час"; | |||
if (hours > 1 && hours < 5) result += "а"; | |||
if (hours >= 5) result += "ов"; | |||
if (remainingMinutes > 0) { | |||
result += " " + remainingMinutes + " минут"; | |||
} | |||
return result; | |||
} | |||
}, | |||
showResult: function (result) { | |||
var resultDiv = document.querySelector(".calculation-result"); | |||
if (!resultDiv) return; | |||
resultDiv.textContent = result.text; | |||
resultDiv.className = | |||
"calculation-result" + (result.isCritical ? " critical" : ""); | |||
resultDiv.style.display = "block"; | |||
}, | |||
hideResult: function () { | |||
var resultDiv = document.querySelector(".calculation-result"); | |||
if (resultDiv) { | |||
resultDiv.style.display = "none"; | |||
} | |||
}, | |||
observeTables: function () { | |||
// Наблюдаем за динамически добавляемыми таблицами | |||
var observer = new MutationObserver(function (mutations) { | |||
mutations.forEach(function (mutation) { | |||
mutation.addedNodes.forEach(function (node) { | |||
if (node.nodeType === 1) { | |||
if ( | |||
node.classList && | |||
node.classList.contains("citizen-table-wrapper") | |||
) { | |||
setTimeout(function () { | |||
LawCalculatorModule.createModeToggle(); | |||
}, 100); | |||
} else if (node.querySelectorAll) { | |||
var tables = node.querySelectorAll( | |||
".citizen-table-wrapper", | |||
); | |||
if (tables.length > 0) { | |||
setTimeout(function () { | |||
LawCalculatorModule.createModeToggle(); | |||
}, 100); | |||
} | |||
} | |||
} | |||
}); | |||
}); | |||
}); | |||
observer.observe(document.body, { | |||
childList: true, | |||
subtree: true, | |||
}); | |||
}, | |||
}; | |||
var SnowflakesModule = { | |||
snowflakes: [], | |||
createInterval: null, | |||
isActive: false, | |||
snowflakeCount: 30, | |||
random: function (min, max) { | |||
return Math.random() * (max - min) + min; | |||
}, | |||
init: function () { | |||
if (this.isActive) return; | |||
this.addStyles(); | |||
this.startContinuousCreation(); | |||
this.isActive = true; | |||
}, | |||
addStyles: function () { | |||
if (document.getElementById("snowflakes-styles")) return; | |||
var style = document.createElement("style"); | |||
style.id = "snowflakes-styles"; | |||
style.textContent = | |||
".snowflake {" + | |||
"position: fixed;" + | |||
"top: -10px;" + | |||
"color: #fff;" + | |||
"font-family: Arial, sans-serif;" + | |||
"user-select: none;" + | |||
"pointer-events: none;" + | |||
"z-index: 1;" + | |||
"animation-name: snowflake-fall;" + | |||
"animation-timing-function: linear;" + | |||
"animation-iteration-count: infinite;" + | |||
"will-change: transform;" + | |||
"}" + | |||
"@keyframes snowflake-fall {" + | |||
"0% {" + | |||
"transform: translateY(0) translateX(0) rotate(0deg);" + | |||
"}" + | |||
"25% {" + | |||
"transform: translateY(25vh) translateX(10px) rotate(90deg);" + | |||
"}" + | |||
"50% {" + | |||
"transform: translateY(50vh) translateX(-10px) rotate(180deg);" + | |||
"}" + | |||
"75% {" + | |||
"transform: translateY(75vh) translateX(10px) rotate(270deg);" + | |||
"}" + | |||
"100% {" + | |||
"transform: translateY(calc(100vh + 100px)) translateX(0) rotate(360deg);" + | |||
"}" + | |||
"}" + | |||
".snowflake.flare {" + | |||
"text-shadow: 0 0 5px rgba(255, 255, 255, 0.4), 0 0 10px rgba(255, 255, 255, 0.2);" + | |||
"}" + | |||
".snowflake.blurred {" + | |||
"filter: blur(2px);" + | |||
"}"; | |||
document.head.appendChild(style); | |||
}, | |||
createSnowflake: function () { | |||
var snowflake = document.createElement("div"); | |||
snowflake.className = "snowflake"; | |||
var symbol = this.getSnowflakeSymbol(); | |||
snowflake.textContent = symbol; | |||
var fontSize = this.random(0.5, 1.2); | |||
snowflake.style.fontSize = fontSize + "em"; | |||
snowflake.style.left = this.random(0, 100) + "vw"; | |||
snowflake.style.opacity = this.random(0.25, 0.5); | |||
var fallDuration = this.random(10, 30); | |||
snowflake.style.animationDuration = fallDuration + "s"; | |||
snowflake.style.animationDelay = this.random(0, 2) + "s"; | |||
if (Math.random() > 0.95) { | |||
snowflake.classList.add("flare"); | |||
} | |||
if (Math.random() > 0.4) { | |||
snowflake.classList.add("blurred"); | |||
} | |||
document.body.appendChild(snowflake); | |||
this.snowflakes.push(snowflake); | |||
var self = this; | |||
var checkRemove = setInterval(function () { | |||
var rect = snowflake.getBoundingClientRect(); | |||
if (rect.top > window.innerHeight + 50) { | |||
if (snowflake.parentNode) { | |||
snowflake.remove(); | |||
} | |||
var index = self.snowflakes.indexOf(snowflake); | |||
if (index > -1) { | |||
self.snowflakes.splice(index, 1); | |||
} | |||
clearInterval(checkRemove); | |||
} | |||
}, 100); | |||
}, | |||
getSnowflakeSymbol: function () { | |||
var symbols = ["❄", "❅", "❆"]; | |||
return symbols[Math.floor(Math.random() * symbols.length)]; | |||
}, | |||
startContinuousCreation: function () { | |||
var self = this; | |||
this.createInterval = setInterval(function () { | |||
if ( | |||
self.isActive && | |||
self.snowflakes.length < self.snowflakeCount * 1.5 | |||
) { | |||
self.createSnowflake(); | |||
} | |||
}, 500); | |||
}, | |||
stop: function () { | |||
this.isActive = false; | |||
if (this.createInterval) { | |||
clearInterval(this.createInterval); | |||
this.createInterval = null; | |||
} | |||
this.snowflakes.forEach(function (snowflake) { | |||
if (snowflake && snowflake.parentNode) { | |||
snowflake.remove(); | |||
} | } | ||
}); | |||
this.snowflakes = []; | |||
var styles = document.getElementById("snowflakes-styles"); | |||
if (styles) { | |||
styles.remove(); | |||
} | |||
}, | |||
toggle: function () { | |||
if (this.isActive) { | |||
this.stop(); | |||
} else { | |||
this.init(); | |||
} | |||
}, | |||
}; | |||
window.SnowflakesModule = SnowflakesModule; | |||
var FireworksModule = { | |||
fireworks: null, | |||
container: null, | |||
checkInterval: null, | |||
isActive: false, | |||
loaded: false, | |||
loading: false, | |||
callbacks: [], | |||
init: function () { | |||
this.startTimeCheck(); | |||
}, | |||
loadFireworks: function (callback) { | |||
// Проверяем разные варианты экспорта библиотеки | |||
if ( | |||
this.loaded && | |||
(window.Fireworks || | |||
(window.Fireworks && window.Fireworks.default)) | |||
) { | |||
if (callback) callback(); | |||
return; | |||
} | |||
if (callback) { | |||
this.callbacks.push(callback); | |||
} | |||
if (this.loading) return; | |||
this.loading = true; | |||
var self = this; | |||
// Загружаем библиотеку fireworks.js через UMD версию | |||
var script = document.createElement("script"); | |||
script.src = "https://unpkg.com/fireworks-js@2/dist/index.umd.js"; | |||
script.onload = function () { | |||
self.loaded = true; | |||
self.loading = false; | |||
// Вызываем все колбэки | |||
self.callbacks.forEach(function (cb) { | |||
cb(); | |||
}); | |||
self.callbacks = []; | |||
}; | |||
script.onerror = function () { | |||
console.error("Ошибка загрузки fireworks.js"); | |||
self.loading = false; | |||
self.callbacks = []; | |||
}; | |||
document.head.appendChild(script); | |||
}, | |||
getMoscowTime: function () { | |||
// МСК = UTC+3 | |||
var now = new Date(); | |||
var utc = now.getTime() + now.getTimezoneOffset() * 60000; | |||
var moscowTime = new Date(utc + 3 * 3600000); // UTC+3 | |||
return moscowTime; | |||
}, | |||
isFireworksTime: function () { | |||
var moscowTime = this.getMoscowTime(); | |||
var hours = moscowTime.getHours(); | |||
// С 00:00 до 01:00 по МСК | |||
return hours === 0; | |||
}, | |||
startTimeCheck: function () { | |||
var self = this; | |||
// Проверяем сразу при загрузке | |||
this.checkTime(); | |||
// Проверяем каждую минуту | |||
this.checkInterval = setInterval(function () { | |||
self.checkTime(); | |||
}, 60000); | |||
}, | |||
checkTime: function () { | |||
if (this.isFireworksTime()) { | |||
if (!this.isActive) { | |||
this.startFireworks(); | |||
} | |||
} else { | |||
if (this.isActive) { | |||
this.stopFireworks(); | |||
} | |||
} | |||
}, | |||
getFireworksConstructor: function () { | |||
// Пробуем разные варианты доступа к конструктору | |||
if (window.Fireworks && typeof window.Fireworks === "function") { | |||
return window.Fireworks; | |||
} | |||
if ( | |||
window.Fireworks && | |||
window.Fireworks.default && | |||
typeof window.Fireworks.default === "function" | |||
) { | |||
return window.Fireworks.default; | |||
} | |||
if (window.fireworks && typeof window.fireworks === "function") { | |||
return window.fireworks; | |||
} | |||
// Если библиотека экспортируется по-другому | |||
if ( | |||
typeof Fireworks !== "undefined" && | |||
typeof Fireworks === "function" | |||
) { | |||
return Fireworks; | |||
} | |||
return null; | |||
}, | |||
startFireworks: function () { | |||
var self = this; | |||
this.loadFireworks(function () { | |||
if (!self.container) { | |||
self.container = document.createElement("div"); | |||
self.container.id = "fireworks-container"; | |||
self.container.style.cssText = | |||
"position: fixed;" + | |||
"top: 0;" + | |||
"left: 0;" + | |||
"width: 100%;" + | |||
"height: 100%;" + | |||
"pointer-events: none;" + | |||
"z-index: 99998;"; | |||
document.body.appendChild(self.container); | |||
} | |||
var FireworksConstructor = self.getFireworksConstructor(); | |||
if (!FireworksConstructor) { | |||
console.error( | |||
"Не удалось найти конструктор Fireworks. Проверьте загрузку библиотеки.", | |||
); | |||
console.log("Доступные объекты:", { | |||
"window.Fireworks": window.Fireworks, | |||
"window.Fireworks.default": | |||
window.Fireworks && window.Fireworks.default, | |||
"window.fireworks": window.fireworks, | |||
}); | |||
return; | |||
} | |||
if (!self.fireworks) { | |||
try { | |||
self.fireworks = new FireworksConstructor( | |||
self.container, | |||
{ | |||
autoresize: true, | |||
opacity: 0.5, | |||
// Замедляем фейерверки | |||
acceleration: 1.02, // Уменьшено с 1.05 для более медленного движения | |||
friction: 0.98, // Увеличено с 0.97 для большего сопротивления | |||
gravity: 1.2, // Уменьшено с 1.5 для более медленного падения | |||
particles: 50, | |||
traceLength: 5, // Увеличено с 3 для более длинного следа | |||
traceSpeed: 5, // Уменьшено с 10 для более медленного следа | |||
explosion: 5, | |||
intensity: 30, | |||
flickering: 50, | |||
lineStyle: "round", | |||
hue: { min: 0, max: 360 }, | |||
// Увеличиваем задержку между запусками для более медленного темпа | |||
delay: { min: 30, max: 60 }, // Увеличено с 15-30 до 30-60 | |||
rocketsPoint: { min: 50, max: 50 }, | |||
lineWidth: { | |||
explosion: { min: 1, max: 3 }, | |||
trace: { min: 1, max: 2 }, | |||
}, | |||
brightness: { min: 50, max: 80 }, | |||
decay: { min: 0.01, max: 0.02 }, // Уменьшено для более медленного затухания | |||
mouse: { click: false, move: false, max: 1 }, | |||
// Включаем звук | |||
sound: { | |||
enable: true, | |||
files: [ | |||
"https://fireworks.js.org/sounds/explosion0.mp3", | |||
"https://fireworks.js.org/sounds/explosion1.mp3", | |||
"https://fireworks.js.org/sounds/explosion2.mp3", | |||
], | |||
volume: { min: 0.3, max: 0.6 }, // Громкость от 30% до 60% | |||
}, | |||
}, | |||
); | |||
} catch (e) { | |||
console.error( | |||
"Ошибка создания экземпляра Fireworks:", | |||
e, | |||
); | |||
return; | |||
} | |||
} | |||
if (self.fireworks) { | |||
self.fireworks.start(); | |||
self.isActive = true; | |||
} | |||
}); | |||
}, | |||
stopFireworks: function () { | |||
if (this.fireworks) { | |||
this.fireworks.stop(); | |||
this.isActive = false; | |||
} | |||
}, | |||
toggleFireworks: function () { | |||
if (this.isActive) { | |||
this.stopFireworks(); | |||
} else { | |||
this.startFireworks(); | |||
} | |||
}, | |||
}; | |||
window.FireworksModule = FireworksModule; | |||
function initAllModules() { | function initAllModules() { | ||
| Строка 2033: | Строка 2417: | ||
// LawCalculatorModule.init(); | // LawCalculatorModule.init(); | ||
} | } | ||
if (document.readyState === | if (document.readyState === "loading") { | ||
document.addEventListener( | document.addEventListener("DOMContentLoaded", initAllModules); | ||
} else { | } else { | ||
initAllModules(); | initAllModules(); | ||
} | } | ||
})(); | })(); | ||
Версия от 18:06, 6 февраля 2026
(function () {
"use strict";
window.MediaWikiTooltips = {
loaded: false,
loading: false,
callbacks: [],
load: function (callback) {
if (this.loaded && window.tippy) {
callback();
return;
}
this.callbacks.push(callback);
if (this.loading) return;
this.loading = true;
var self = this;
var cdns = [
{ base: "https://unpkg.com", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" },
{ base: "https://cdn.jsdelivr.net/npm", popper: "/@popperjs/core@2/dist/umd/popper.min.js", tippy: "/tippy.js@6/dist/tippy-bundle.umd.min.js", css: "/tippy.js@6/dist/tippy.css" }
];
var cdnIndex = 0;
var tryFallback = function (which) {
self.loading = false;
console.error(
"[MediaWikiTooltips] Не удалось загрузить " + which + ". " +
"Возможные причины: CSP вики блокирует внешние скрипты, нет сети, или CDN недоступен."
);
self.callbacks = [];
};
var runCallbacks = function () {
self.loaded = true;
self.loading = false;
self.callbacks.forEach(function (cb) {
try { cb(); } catch (e) { console.error("[MediaWikiTooltips] callback error:", e); }
});
self.callbacks = [];
};
function loadTippyAndCss() {
var cdn = cdns[cdnIndex];
var tippyScript = document.createElement("script");
tippyScript.src = cdn.base + cdn.tippy;
tippyScript.onerror = function () { tryFallback("Tippy.js"); };
tippyScript.onload = function () {
if (!window.tippy) {
tryFallback("Tippy.js (не в глобальной области)");
return;
}
var tippyCSS = document.createElement("link");
tippyCSS.rel = "stylesheet";
tippyCSS.href = cdn.base + cdn.css;
tippyCSS.onerror = function () { console.warn("[MediaWikiTooltips] Стили Tippy не загрузились."); };
document.head.appendChild(tippyCSS);
console.log("[MediaWikiTooltips] Tippy.js загружен с", cdn.base);
runCallbacks();
};
document.head.appendChild(tippyScript);
}
function loadPopper() {
var cdn = cdns[cdnIndex];
var popperScript = document.createElement("script");
popperScript.src = cdn.base + cdn.popper;
popperScript.onerror = function () {
if (cdnIndex + 1 < cdns.length) {
cdnIndex++;
console.warn("[MediaWikiTooltips] Повтор с CDN:", cdns[cdnIndex].base);
loadPopper();
} else {
tryFallback("Popper.js");
}
};
popperScript.onload = loadTippyAndCss;
document.head.appendChild(popperScript);
}
loadPopper();
},
};
var SidebarModule = {
init: function () {
var buttons = document.querySelectorAll(".боковая-панель-кнопка");
var sections = document.querySelectorAll(".боковая-панель-раздел");
if (buttons.length === 0) return;
buttons.forEach(function (button) {
button.addEventListener("click", function () {
var targetId = this.dataset.target;
buttons.forEach(function (btn) {
btn.classList.remove("active");
});
this.classList.add("active");
sections.forEach(function (section) {
section.classList.remove("default");
});
var targetSection = document.getElementById(targetId);
if (targetSection) {
targetSection.classList.add("default");
}
});
});
buttons[0].click();
},
};
var AccessTooltipsModule = {
init: function () {
var containers = document.querySelectorAll(".допуск-контейнер");
if (containers.length === 0) return;
MediaWikiTooltips.load(function () {
containers.forEach(function (container) {
var contentElement =
container.querySelector(".допуск-подсказка");
if (!contentElement) return;
var content = contentElement.innerHTML;
tippy(container, {
content: content,
allowHTML: true,
interactive: true,
placement: "auto",
maxWidth: 500,
theme: "dark",
arrow: true,
duration: [200, 200],
popperOptions: {
modifiers: [
{
name: "preventOverflow",
options: { padding: 8 },
},
{
name: "flip",
options: {
fallbackPlacements: [
"top",
"bottom",
"right",
"left",
],
},
},
],
},
});
});
});
},
};
var LawTooltipsModule = {
init: function () {
console.log("[LawTooltips] init()");
var tableCells = document.querySelectorAll(
".law-tooltips td, .law-tooltips th",
);
console.log(
"[LawTooltips] .law-tooltips ячеек:",
tableCells.length,
);
if (tableCells.length === 0) {
console.warn("[LawTooltips] Нет ячеек .law-tooltips — выход");
return;
}
function run() {
console.log("[LawTooltips] run() — извлечение данных...");
var lawData = LawTooltipsModule.extractLawData();
var count = Object.keys(lawData).length;
console.log(
"[LawTooltips] Загружено записей законов:",
count,
count
? Object.keys(lawData).slice(0, 10).join(", ") +
(count > 10 ? "..." : "")
: "",
);
if (count > 0) {
LawTooltipsModule.initCellTooltips(tableCells, lawData);
return true;
}
return false;
}
if (typeof MediaWikiTooltips === "undefined") {
console.error(
"[LawTooltips] MediaWikiTooltips не найден — тултипы не загрузятся",
);
run();
return;
}
MediaWikiTooltips.load(function () {
console.log("[LawTooltips] MediaWikiTooltips.load вызван");
if (run()) {
console.log("[LawTooltips] Инициализация с первой попытки");
return;
}
setTimeout(function () {
if (run()) {
console.log("[LawTooltips] Инициализация после 300ms");
return;
}
setTimeout(function () {
if (run())
console.log(
"[LawTooltips] Инициализация после 800ms",
);
else
console.warn(
"[LawTooltips] Данные законов не найдены после всех попыток",
);
}, 500);
}, 300);
});
},
extractLawData: function () {
var lawData = {};
var containers = document.querySelectorAll(".mw-collapsible");
console.log(
"[LawTooltips] extractLawData: коллапсибл-блоков:",
containers.length,
);
containers.forEach(function (block) {
var content = block.querySelector(".mw-collapsible-content");
if (content && block.classList.contains("mw-collapsed")) {
block.classList.remove("mw-collapsed");
var toggle = content.querySelector(
".mw-collapsible-toggle",
);
if (toggle) toggle.setAttribute("aria-expanded", "true");
}
});
var detailTables = document.querySelectorAll(
".mw-collapsible-content table",
);
console.log(
"[LawTooltips] extractLawData: таблиц в .mw-collapsible-content:",
detailTables.length,
);
detailTables.forEach(function (table, tableIndex) {
if (table.classList.contains("law-tooltips")) {
console.log(
"[LawTooltips] extractLawData: таблица",
tableIndex,
"— пропуск (law-tooltips)",
);
return;
}
var rows = table.querySelectorAll("tr");
rows.forEach(function (row) {
var cells = row.querySelectorAll("td");
if (cells.length < 3) return;
var crimeCell = cells[0];
var descriptionCell = cells[1];
var exampleCell = cells[2];
var anchor = crimeCell.querySelector("[id]");
if (!anchor) return;
var lawCode = anchor.id;
var titleElement = crimeCell.querySelector("b");
if (!titleElement || !lawCode.match(/^\d{3}$/)) return;
var titleText = titleElement.textContent.trim();
var lawTitle = titleText
.replace(/^\d{3}\s*/, "")
.replace(/^\d{3}/, "")
.trim();
var description = descriptionCell.innerHTML.trim();
var examples = [];
var exampleItems = exampleCell.querySelectorAll("li");
exampleItems.forEach(function (item) {
var html = item.innerHTML
.trim()
.replace(/<br\s*\/?>/gi, "");
if (html) examples.push(html);
});
lawData[lawCode] = {
title: lawTitle,
description: description,
examples: examples,
};
});
});
console.log(
"[LawTooltips] extractLawData: итого кодов:",
Object.keys(lawData).length,
);
return lawData;
},
createTooltipContent: function (lawCode, lawData) {
var data = lawData[lawCode];
if (!data) return null;
var html = '<div class="law-tooltip">';
html +=
'<h4 class="law-tooltip-title">' +
lawCode +
" - " +
data.title +
"</h4>";
html +=
'<p class="law-tooltip-description">' +
data.description +
"</p>";
if (data.examples && data.examples.length > 0) {
html += '<div class="law-tooltip-examples">';
html += "<strong>Примеры:</strong><ul>";
data.examples.slice(0, 5).forEach(function (example) {
html += "<li>" + example + "</li>";
});
html += "</ul></div>";
}
html += "</div>";
return html;
},
initCellTooltips: function (tableCells, lawData) {
var withLink = 0,
withMatch = 0,
withContent = 0,
tooltipsCreated = 0;
tableCells.forEach(function (cell) {
var link = cell.querySelector('a[href*="#"]');
if (!link) return;
withLink++;
var href = link.getAttribute("href");
var match = href.match(/#(\d{3})/);
if (!match) return;
withMatch++;
var lawCode = match[1];
var tooltipContent = LawTooltipsModule.createTooltipContent(
lawCode,
lawData,
);
if (!tooltipContent) return;
withContent++;
cell.classList.add("law-cell-with-tooltip");
tooltipsCreated++;
cell.addEventListener("click", function (e) {
e.preventDefault();
window.location.hash = lawCode;
});
if (typeof tippy === "undefined") {
console.error(
"[LawTooltips] tippy не найден — подключи библиотеку Tippy.js",
);
return;
}
tippy(cell, {
content: tooltipContent,
allowHTML: true,
interactive: true,
placement: "top",
maxWidth: 400,
theme: "law-theme",
arrow: true,
duration: [200, 150],
delay: [0, 50],
appendTo: document.body,
boundary: "viewport",
popperOptions: {
strategy: "fixed",
modifiers: [
{
name: "preventOverflow",
options: {
boundary: "viewport",
padding: 20,
altAxis: true,
altBoundary: true,
},
},
{
name: "flip",
options: {
fallbackPlacements: [
"bottom",
"left",
"right",
],
},
},
{
name: "computeStyles",
options: { adaptive: false },
},
],
},
onShow: function (instance) {
document
.querySelectorAll("[data-tippy-root]")
.forEach(function (tooltip) {
if (
tooltip._tippy &&
tooltip._tippy !== instance
) {
tooltip._tippy.hide();
}
});
},
});
});
console.log(
"[LawTooltips] initCellTooltips: ячеек с ссылкой",
withLink,
"| с #XXX",
withMatch,
"| с данными",
withContent,
"| тултипов создано",
tooltipsCreated,
);
},
};
var DataTooltipsModule = {
init: function () {
var elements = document.querySelectorAll(
".data-tooltip[data-text]",
);
if (elements.length === 0) return;
MediaWikiTooltips.load(function () {
elements.forEach(function (element) {
var tooltipText = element.getAttribute("data-text");
if (!tooltipText || !tooltipText.trim()) return;
tippy(element, {
content: tooltipText,
allowHTML: true,
interactive: false,
placement: "top",
maxWidth: 300,
theme: "data-tooltip-theme",
arrow: true,
duration: [200, 150],
delay: [0, 50],
});
});
});
var observer = new MutationObserver(function (mutations) {
var shouldReinit = false;
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
if (
node.classList &&
node.classList.contains("data-tooltip") &&
node.hasAttribute("data-text")
) {
shouldReinit = true;
} else if (node.querySelectorAll) {
var newElements = node.querySelectorAll(
".data-tooltip[data-text]",
);
if (newElements.length > 0) {
shouldReinit = true;
}
}
}
});
});
if (shouldReinit) {
setTimeout(function () {
DataTooltipsModule.init();
}, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
},
};
var CopyTextModule = {
init: function () {
CopyTextModule.initDirectElements();
CopyTextModule.initAutoContainers();
},
initDirectElements: function () {
var elements = document.querySelectorAll(".copyable-text");
if (elements.length === 0) return;
elements.forEach(function (element) {
CopyTextModule.cleanPreContent(element);
CopyTextModule.setupElement(element);
});
},
initAutoContainers: function () {
var containers = document.querySelectorAll(
".copyable-pre-container",
);
if (containers.length === 0) return;
containers.forEach(function (container) {
CopyTextModule.processContainerPre(container);
});
// Наблюдатель за динамически появляющимися элементами
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
// Проверяем сам элемент
if (node.tagName === "PRE") {
var container = node.closest(
".copyable-pre-container",
);
if (
container &&
!node.classList.contains("copyable-text")
) {
node.classList.add("copyable-text");
CopyTextModule.cleanPreContent(node);
CopyTextModule.setupElement(node);
}
}
// Проверяем дочерние элементы
else if (node.querySelectorAll) {
var containers = node.querySelectorAll(
".copyable-pre-container",
);
containers.forEach(function (container) {
CopyTextModule.processContainerPre(
container,
);
});
// Также проверяем pre внутри уже существующих контейнеров
var preElements = node.querySelectorAll(
".copyable-pre-container pre",
);
preElements.forEach(function (pre) {
if (
!pre.classList.contains("copyable-text")
) {
pre.classList.add("copyable-text");
CopyTextModule.cleanPreContent(pre);
CopyTextModule.setupElement(pre);
}
});
}
}
});
// Обрабатываем изменения атрибутов (например, style="display: none" -> "")
if (
mutation.type === "attributes" &&
mutation.attributeName === "style"
) {
var target = mutation.target;
if (target.style.display !== "none") {
var containers = target.querySelectorAll(
".copyable-pre-container",
);
containers.forEach(function (container) {
CopyTextModule.processContainerPre(container);
});
}
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["style"],
});
},
processContainerPre: function (container) {
var preElements = container.querySelectorAll("pre");
preElements.forEach(function (pre) {
if (!pre.classList.contains("copyable-text")) {
pre.classList.add("copyable-text");
CopyTextModule.cleanPreContent(pre);
CopyTextModule.setupElement(pre);
}
});
},
cleanPreContent: function (preElement) {
var text = preElement.textContent || preElement.innerText;
var cleanedText = text
.split("\n")
.map(function (line) {
return line.replace(/^\s*\|\s*/, "");
})
.join("\n");
if (cleanedText !== text) {
preElement.textContent = cleanedText;
}
},
setupElement: function (element) {
element.style.cursor = "pointer";
element.addEventListener("click", CopyTextModule.handleClick);
var isIOS =
/iPad|iPhone|iPod/.test(navigator.userAgent) &&
!window.MSStream;
if (isIOS) {
element.style.webkitUserSelect = "text";
element.style.userSelect = "text";
}
},
handleClick: function (e) {
e.preventDefault();
var element = this;
var textToCopy = element.textContent;
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(textToCopy)
.then(function () {
CopyTextModule.showNotification(element);
})
.catch(function (err) {
console.error("Ошибка при копировании: ", err);
CopyTextModule.fallbackCopy(textToCopy, element);
});
} else {
CopyTextModule.fallbackCopy(textToCopy, element);
}
element.classList.add("copying");
setTimeout(function () {
element.classList.remove("copying");
}, 200);
},
fallbackCopy: function (text, element) {
var textArea = document.createElement("textarea");
textArea.value = text;
textArea.style.position = "fixed";
textArea.style.top = "-9999px";
textArea.style.left = "-9999px";
textArea.style.width = "2em";
textArea.style.height = "2em";
textArea.style.padding = "0";
textArea.style.border = "none";
textArea.style.outline = "none";
textArea.style.boxShadow = "none";
textArea.style.background = "transparent";
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand("copy");
if (successful) {
CopyTextModule.showNotification(element);
} else {
console.error("Не удалось скопировать текст");
}
} catch (err) {
console.error("Ошибка при копировании: ", err);
}
document.body.removeChild(textArea);
},
showNotification: function (element) {
var existingNotifications =
document.querySelectorAll(".copy-notification");
existingNotifications.forEach(function (notification) {
notification.remove();
});
var notification = document.createElement("div");
notification.className = "copy-notification";
notification.textContent = "Текст скопирован!";
document.body.appendChild(notification);
var rect = element.getBoundingClientRect();
var isMobile = window.innerWidth <= 768;
if (isMobile) {
notification.style.position = "fixed";
notification.style.top = "20px";
notification.style.left = "50%";
notification.style.transform =
"translateX(-50%) translateY(-10px)";
notification.style.zIndex = "10000";
} else {
notification.style.position = "absolute";
notification.style.top = rect.top + window.scrollY - 40 + "px";
notification.style.left = rect.left + window.scrollX + "px";
notification.style.zIndex = "9999";
}
setTimeout(function () {
notification.classList.add("show");
}, 10);
setTimeout(function () {
notification.classList.remove("show");
setTimeout(function () {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}, 2000);
},
};
var DocumentAutoFillModule = {
init: function () {
this.processExistingContainers();
this.observeNewContainers();
this.startTimeUpdateTimer();
},
processExistingContainers: function () {
var containers = document.querySelectorAll(
".copyable-pre-container.auto-fill-enabled",
);
containers.forEach(
function (container) {
this.setupAutoFillContainer(container);
}.bind(this),
);
},
setupAutoFillContainer: function (container) {
if (container.querySelector(".document-fields-container")) {
return;
}
var fieldsContainer = document.createElement("div");
fieldsContainer.className = "document-fields-container";
var gridContainer = document.createElement("div");
gridContainer.className = "fields-grid";
var siteNumberField = this.createField(
"site-number",
"Номер участка:",
"Введите номер участка...",
false,
);
var authorField = this.createField(
"author",
"Автор документа:",
"Введите ваше имя...",
true,
);
var positionField = this.createField(
"position",
"Должность:",
"Введите вашу должность...",
true,
);
var signatureField = this.createField(
"signature",
"Подпись:",
"Введите подпись...",
true,
);
gridContainer.appendChild(siteNumberField.container);
gridContainer.appendChild(authorField.container);
gridContainer.appendChild(positionField.container);
gridContainer.appendChild(signatureField.container);
fieldsContainer.appendChild(gridContainer);
if (container.firstChild) {
container.insertBefore(fieldsContainer, container.firstChild);
} else {
container.appendChild(fieldsContainer);
}
setTimeout(
function () {
this.applyAllFieldsToContainer(container);
}.bind(this),
100,
);
},
createField: function (fieldType, labelText, placeholder, shouldCache) {
var fieldContainer = document.createElement("div");
fieldContainer.className = "field-container";
var label = document.createElement("label");
label.textContent = labelText;
label.htmlFor = fieldType + "-input";
var input = document.createElement("input");
input.type = "text";
input.id = fieldType + "-input";
input.className = "field-input " + fieldType + "-input";
input.placeholder = placeholder;
if (shouldCache) {
var savedValue = localStorage.getItem(
"document" + this.capitalizeFirstLetter(fieldType),
);
if (savedValue) {
input.value = savedValue;
}
}
input.addEventListener(
"input",
function () {
var value = input.value.trim();
if (shouldCache) {
localStorage.setItem(
"document" + this.capitalizeFirstLetter(fieldType),
value,
);
}
}.bind(this),
);
fieldContainer.appendChild(label);
fieldContainer.appendChild(input);
return {
container: fieldContainer,
input: input,
type: fieldType,
shouldCache: shouldCache,
};
},
capitalizeFirstLetter: function (string) {
return string.charAt(0).toUpperCase() + string.slice(1);
},
applyAllFieldsToContainer: function (container) {
var fieldsContainer = container.querySelector(
".document-fields-container",
);
if (!fieldsContainer) return;
var siteNumber = fieldsContainer
.querySelector(".site-number-input")
.value.trim();
var author = fieldsContainer
.querySelector(".author-input")
.value.trim();
var position = fieldsContainer
.querySelector(".position-input")
.value.trim();
var signature = fieldsContainer
.querySelector(".signature-input")
.value.trim();
this.autoFillAllDocumentsInContainer(container, {
siteNumber: siteNumber,
author: author,
position: position,
signature: signature,
});
},
autoFillAllDocumentsInContainer: function (container, fields) {
var preElements = container.querySelectorAll("pre");
preElements.forEach(
function (preElement) {
this.autoFillDocument(preElement, fields);
}.bind(this),
);
},
autoFillDocument: function (preElement, fields) {
if (!preElement) return;
var originalContent =
preElement.textContent || preElement.innerText;
if (!this.isDocumentTemplate(originalContent)) {
return;
}
var updatedContent = this.fillDocumentContent(
originalContent,
fields,
);
if (updatedContent !== originalContent) {
preElement.textContent = updatedContent;
}
},
isDocumentTemplate: function (text) {
return (
text.includes("SCP") &&
text.includes("Обезопасить. Удержать. Сохранить.") &&
text.includes("Автор документа:") &&
text.includes("Дата и время:")
);
},
fillDocumentContent: function (content, fields) {
var currentDateTime = this.getCurrentDateTime();
content = content.replace(
/(\[bold\]Дата и время:\[\/bold\])([^\n]*)/g,
"$1 " + currentDateTime,
);
if (fields.siteNumber) {
content = content.replace(
/Участок-([^|\n\[\]]*)/g,
"Участок-" + fields.siteNumber + " ",
);
}
if (fields.author) {
content = content.replace(
/(\[bold\]Автор документа:\[\/bold\])([^\n]*)/g,
"$1 " + fields.author,
);
} else {
content = content.replace(
/(\[bold\]Автор документа:\[\/bold\])([^\n]*)/g,
"$1",
);
}
if (fields.position) {
content = content.replace(
/(\[bold\]Должность:\[\/bold\])([^\n]*)/g,
"$1 " + fields.position,
);
} else {
content = content.replace(
/(\[bold\]Должность:\[\/bold\])([^\n]*)/g,
"$1",
);
}
if (fields.signature) {
content = content.replace(
/(\[bold\]Подпись:\[\/bold\])[^\n]*/g,
"$1 " + fields.signature,
);
} else {
content = content.replace(
/(\[bold\]Подпись:\[\/bold\])([^\n]*)/g,
"$1_____",
);
}
return content;
},
getCurrentDateTime: function () {
var now = new Date();
var day = String(now.getDate()).padStart(2, "0");
var month = String(now.getMonth() + 1).padStart(2, "0");
var year = now.getFullYear();
var hours = String(now.getHours()).padStart(2, "0");
var minutes = String(now.getMinutes()).padStart(2, "0");
return (
hours + ":" + minutes + ", " + day + "." + month + "." + year
);
},
startTimeUpdateTimer: function () {
var self = this;
function updateTimeIfNeeded() {
var containers = document.querySelectorAll(
".copyable-pre-container.auto-fill-enabled",
);
if (containers.length > 0) {
containers.forEach(function (container) {
var fieldsContainer = container.querySelector(
".document-fields-container",
);
if (fieldsContainer) {
self.applyAllFieldsToContainer(container);
}
});
}
}
this.timeUpdateInterval = setInterval(updateTimeIfNeeded, 5000);
document.addEventListener("visibilitychange", function () {
if (!document.hidden) {
updateTimeIfNeeded();
}
});
window.addEventListener("focus", updateTimeIfNeeded);
},
stopTimeUpdateTimer: function () {
if (this.timeUpdateInterval) {
clearInterval(this.timeUpdateInterval);
this.timeUpdateInterval = null;
}
},
observeNewContainers: function () {
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
if (
node.classList &&
node.classList.contains(
"copyable-pre-container",
) &&
node.classList.contains("auto-fill-enabled")
) {
setTimeout(function () {
DocumentAutoFillModule.setupAutoFillContainer(
node,
);
}, 100);
} else if (node.querySelectorAll) {
var containers = node.querySelectorAll(
".copyable-pre-container.auto-fill-enabled",
);
containers.forEach(function (container) {
setTimeout(function () {
DocumentAutoFillModule.setupAutoFillContainer(
container,
);
}, 100);
});
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
},
};
window.DocumentAutoFill = {
refreshAll: function () {
var containers = document.querySelectorAll(
".copyable-pre-container.auto-fill-enabled",
);
containers.forEach(function (container) {
DocumentAutoFillModule.applyAllFieldsToContainer(container);
});
},
clearAllNames: function () {
localStorage.removeItem("documentAuthor");
localStorage.removeItem("documentPosition");
localStorage.removeItem("documentSignature");
var inputs = document.querySelectorAll(
".author-input, .position-input, .signature-input",
);
inputs.forEach(function (input) {
input.value = "";
});
window.DocumentAutoFill.refreshAll();
},
startTimeUpdates: function () {
DocumentAutoFillModule.startTimeUpdateTimer();
},
stopTimeUpdates: function () {
DocumentAutoFillModule.stopTimeUpdateTimer();
},
};
var LawCalculatorModule = {
isCalculatorMode: false,
selectedViolations: [],
lawData: {},
init: function () {
this.extractLawData();
this.createModeToggle();
this.observeTables();
},
extractLawData: function () {
// Используем данные из существующего LawTooltipsModule
if (
window.LawTooltipsModule &&
window.LawTooltipsModule.extractLawData
) {
this.lawData = window.LawTooltipsModule.extractLawData();
} else {
// Fallback - извлекаем данные самостоятельно
this.lawData = this.extractLawDataFallback();
}
// Добавляем информацию о категориях и сроках
this.addCategoryInfo();
},
extractLawDataFallback: function () {
var lawData = {};
var detailTables = document.querySelectorAll(
".mw-collapsible-content table",
);
detailTables.forEach(function (table) {
var rows = table.querySelectorAll("tr");
rows.forEach(function (row) {
var cells = row.querySelectorAll("td");
if (cells.length < 3) return;
var crimeCell = cells[0];
var anchor = crimeCell.querySelector("[id]");
if (!anchor) return;
var lawCode = anchor.id;
var titleElement = crimeCell.querySelector("b");
if (titleElement && lawCode.match(/^\d{3}$/)) {
var titleText = titleElement.textContent.trim();
var lawTitle = titleText
.replace(/^\d{3}\s*/, "")
.replace(/^\d{3}/, "")
.trim();
var description = cells[1].innerHTML.trim();
var examples = [];
var exampleItems = cells[2].querySelectorAll("li");
exampleItems.forEach(function (item) {
var html = item.innerHTML.trim();
html = html.replace(/<br\s*\/?>/gi, "");
if (html) examples.push(html);
});
lawData[lawCode] = {
title: lawTitle,
description: description,
examples: examples,
};
}
});
});
return lawData;
},
addCategoryInfo: function () {
// Определяем тип страницы и извлекаем информацию о наказаниях
this.pageType = this.detectPageType();
this.extractPunishmentInfo();
},
detectPageType: function () {
// Проверяем заголовки страницы для определения типа
var pageTitle = document.title.toLowerCase();
var headings = document.querySelectorAll("h1, h2, h3");
for (var i = 0; i < headings.length; i++) {
var headingText = headings[i].textContent.toLowerCase();
if (
headingText.includes("д-класс") ||
headingText.includes("d-class")
) {
return "d-class";
}
if (
headingText.includes("персонал") ||
headingText.includes("сотрудник")
) {
return "personnel";
}
}
// Проверяем по URL или другим признакам
if (
window.location.href.toLowerCase().includes("d-class") ||
window.location.href.toLowerCase().includes("д-класс")
) {
return "d-class";
}
return "personnel"; // По умолчанию
},
extractPunishmentInfo: function () {
// Извлекаем информацию о наказаниях из текста на странице
var punishmentSections = document.querySelectorAll(
".mw-collapsible-content p",
);
var categories = {};
console.log(
"LawCalculator: Найдено секций с наказаниями:",
punishmentSections.length,
);
console.log("LawCalculator: Тип страницы:", this.pageType);
punishmentSections.forEach(
function (section) {
var text = section.textContent;
var punishmentMatch = text.match(
/Наказание[:\s]*(.+?)(?:\n|$)/i,
);
if (punishmentMatch) {
var punishmentText = punishmentMatch[1].trim();
console.log(
"LawCalculator: Найдено наказание:",
punishmentText,
);
var categoryInfo =
this.parsePunishmentText(punishmentText);
if (categoryInfo) {
// Находим соответствующую категорию по цвету или другим признакам
var categoryKey =
this.findCategoryByContext(section);
if (categoryKey) {
categories[categoryKey] = categoryInfo;
console.log(
"LawCalculator: Применено к категории:",
categoryKey,
categoryInfo,
);
}
}
}
}.bind(this),
);
console.log("LawCalculator: Итоговые категории:", categories);
// Применяем найденную информацию к законам
this.applyCategoryInfo(categories);
},
parsePunishmentText: function (text) {
// Парсим текст наказания
var originalText = text;
text = text.toLowerCase();
// Проверяем на критические нарушения
if (
text.includes("д-класс") ||
text.includes("казнь") ||
text.includes("перевод")
) {
return {
isCritical: true,
punishment: originalText.trim(),
};
}
// Для Д-класса ищем штрафы
if (this.pageType === "d-class") {
return this.parseDClassPunishment(originalText);
}
// Для персонала ищем временные рамки
return this.parsePersonnelPunishment(originalText);
},
parseDClassPunishment: function (text) {
// Парсим наказания для Д-класса (штрафы, дополнительные наказания)
var lowerText = text.toLowerCase();
// Ищем штрафы в кредитах
var creditMatch = text.match(/(\d+[\s,.]?\d*)\s*кредит/);
if (creditMatch) {
var amount = creditMatch[1].replace(/[\s,]/g, "");
return {
isCritical: false,
punishment: "Штраф " + amount + " кредитов",
fine: parseInt(amount),
};
}
// Ищем временные наказания для Д-класса
var timeMatch = text.match(/(\d+)\s*до\s*(\d+)\s*минут/);
if (timeMatch) {
return {
minTime: parseInt(timeMatch[1]),
maxTime: parseInt(timeMatch[2]),
isCritical: false,
punishment: timeMatch[1] + "-" + timeMatch[2] + " минут",
};
}
var rangeMatch = text.match(/от\s*(\d+)\s*до\s*(\d+)\s*минут/);
if (rangeMatch) {
return {
minTime: parseInt(rangeMatch[1]),
maxTime: parseInt(rangeMatch[2]),
isCritical: false,
punishment: rangeMatch[1] + "-" + rangeMatch[2] + " минут",
};
}
var fixedMatch = text.match(/(\d+)\s*минут/);
if (fixedMatch) {
var time = parseInt(fixedMatch[1]);
return {
minTime: time,
maxTime: time,
isCritical: false,
punishment: time + " минут",
};
}
// Если не нашли конкретные наказания, возвращаем текст как есть
return {
isCritical: false,
punishment: text.trim(),
};
},
parsePersonnelPunishment: function (text) {
// Парсим наказания для персонала
var lowerText = text.toLowerCase();
// Ищем временные рамки
var timeMatch = text.match(/(\d+)\s*до\s*(\d+)\s*минут/);
if (timeMatch) {
return {
minTime: parseInt(timeMatch[1]),
maxTime: parseInt(timeMatch[2]),
isCritical: false,
};
}
// Ищем диапазон времени
var rangeMatch = text.match(/от\s*(\d+)\s*до\s*(\d+)\s*минут/);
if (rangeMatch) {
return {
minTime: parseInt(rangeMatch[1]),
maxTime: parseInt(rangeMatch[2]),
isCritical: false,
};
}
// Ищем фиксированное время
var fixedMatch = text.match(/(\d+)\s*минут/);
if (fixedMatch) {
var time = parseInt(fixedMatch[1]);
return {
minTime: time,
maxTime: time,
isCritical: false,
};
}
return null;
},
findCategoryByContext: function (section) {
// Находим категорию по контексту (заголовок секции)
var collapsible = section.closest(".mw-collapsible");
if (!collapsible) return null;
var toggle = collapsible.querySelector(".mw-collapsible-toggle");
if (!toggle) return null;
var titleElement = toggle.querySelector(".рамка-название");
if (!titleElement) return null;
var title = titleElement.textContent.trim();
// Определяем диапазон статей по заголовку
if (title.includes("Лёгкие")) return "100-109";
if (title.includes("Средние")) return "200-211";
if (title.includes("Тяжкие") && !title.includes("Особо"))
return "300-311";
if (title.includes("Особо тяжкие")) return "400-411";
if (title.includes("Критические")) return "500-511";
return null;
},
applyCategoryInfo: function (categories) {
// Применяем информацию о категориях к законам
Object.keys(this.lawData).forEach(
function (lawCode) {
var code = parseInt(lawCode);
var categoryKey = null;
// Определяем категорию по номеру статьи
if (code >= 100 && code <= 109) categoryKey = "100-109";
else if (code >= 200 && code <= 211)
categoryKey = "200-211";
else if (code >= 300 && code <= 311)
categoryKey = "300-311";
else if (code >= 400 && code <= 411)
categoryKey = "400-411";
else if (code >= 500 && code <= 511)
categoryKey = "500-511";
if (categoryKey && categories[categoryKey]) {
this.lawData[lawCode].category =
categories[categoryKey];
}
}.bind(this),
);
},
createModeToggle: function () {
var tables = document.querySelectorAll(
".citizen-table-wrapper table",
);
tables.forEach(
function (table) {
var wrapper = table.closest(".citizen-table-wrapper");
if (!wrapper || wrapper.querySelector(".law-mode-toggle"))
return;
var toggleContainer = document.createElement("div");
toggleContainer.className = "law-mode-toggle";
toggleContainer.innerHTML =
'<div class="mode-toggle-buttons">' +
'<button class="mode-btn active" data-mode="navigation">Навигация</button>' +
'<button class="mode-btn" data-mode="calculator">Калькулятор</button>' +
"</div>";
wrapper.appendChild(toggleContainer);
// Добавляем стили
this.addToggleStyles();
// Обработчики событий
var buttons = toggleContainer.querySelectorAll(".mode-btn");
buttons.forEach(function (btn) {
btn.addEventListener("click", function () {
buttons.forEach(function (b) {
b.classList.remove("active");
});
this.classList.add("active");
var mode = this.dataset.mode;
LawCalculatorModule.setMode(mode, table);
});
});
}.bind(this),
);
},
addToggleStyles: function () {
if (document.getElementById("law-calculator-styles")) return;
var style = document.createElement("style");
style.id = "law-calculator-styles";
style.textContent =
".law-mode-toggle {" +
"margin: 10px 0;" +
"text-align: center;" +
"}" +
".mode-toggle-buttons {" +
"display: inline-flex;" +
"background: #f0f0f0;" +
"border-radius: 5px;" +
"padding: 2px;" +
"border: 1px solid #ccc;" +
"}" +
".mode-btn {" +
"padding: 8px 16px;" +
"border: none;" +
"background: transparent;" +
"cursor: pointer;" +
"border-radius: 3px;" +
"font-size: 14px;" +
"transition: all 0.2s;" +
"}" +
".mode-btn.active {" +
"background: #007cba;" +
"color: white;" +
"}" +
".mode-btn:hover:not(.active) {" +
"background: #e0e0e0;" +
"}" +
".calculator-interface {" +
"margin-top: 15px;" +
"padding: 15px;" +
"background: #f9f9f9;" +
"border: 1px solid #ddd;" +
"border-radius: 5px;" +
"display: none;" +
"}" +
".calculator-interface.active {" +
"display: block;" +
"}" +
".selected-violations {" +
"margin: 10px 0;" +
"}" +
".violation-item {" +
"display: inline-block;" +
"margin: 5px;" +
"padding: 5px 10px;" +
"background: #e3f2fd;" +
"border: 1px solid #2196f3;" +
"border-radius: 15px;" +
"font-size: 12px;" +
"}" +
".violation-item .remove-btn {" +
"margin-left: 5px;" +
"cursor: pointer;" +
"color: #f44336;" +
"font-weight: bold;" +
"}" +
".calculation-result {" +
"margin-top: 15px;" +
"padding: 10px;" +
"background: #fff3cd;" +
"border: 1px solid #ffeaa7;" +
"border-radius: 5px;" +
"font-weight: bold;" +
"white-space: pre-line;" +
"}" +
".calculation-result.critical {" +
"background: #f8d7da;" +
"border-color: #f5c6cb;" +
"color: #721c24;" +
"}" +
".law-cell-calculator {" +
"cursor: pointer;" +
"transition: background-color 0.2s;" +
"}" +
".law-cell-calculator:hover {" +
"background-color: rgba(0, 124, 186, 0.1) !important;" +
"}" +
".law-cell-calculator.selected {" +
"background-color: rgba(0, 124, 186, 0.3) !important;" +
"}" +
".calculator-actions {" +
"margin-top: 10px;" +
"}" +
".calculator-actions button {" +
"padding: 8px 16px;" +
"border: 1px solid #ccc;" +
"background: white;" +
"cursor: pointer;" +
"border-radius: 3px;" +
"font-size: 14px;" +
"}" +
".calculator-actions button:hover {" +
"background: #f0f0f0;" +
"}" +
".calculator-actions .calculate-btn {" +
"background: #007cba;" +
"color: white;" +
"border-color: #007cba;" +
"}" +
".calculator-actions .calculate-btn:hover {" +
"background: #005a87;" +
"}";
document.head.appendChild(style);
},
setMode: function (mode, table) {
this.isCalculatorMode = mode === "calculator";
var wrapper = table.closest(".citizen-table-wrapper");
var calculatorInterface = wrapper.querySelector(
".calculator-interface",
);
if (this.isCalculatorMode) {
if (!calculatorInterface) {
calculatorInterface = this.createCalculatorInterface();
wrapper.appendChild(calculatorInterface);
}
calculatorInterface.classList.add("active");
this.makeTableInteractive(table);
} else {
if (calculatorInterface) {
calculatorInterface.classList.remove("active");
}
this.makeTableNormal(table);
}
},
createCalculatorInterface: function () {
var calculatorInterface = document.createElement("div");
calculatorInterface.className = "calculator-interface";
calculatorInterface.innerHTML =
"<h4>Калькулятор сроков заключения</h4>" +
"<p>Выберите нарушения, кликая по статьям в таблице выше:</p>" +
'<div class="selected-violations">' +
'<div class="violations-list"></div>' +
"</div>" +
'<div class="calculation-result" style="display: none;"></div>' +
'<div class="calculator-actions">' +
'<button class="clear-btn" style="margin-right: 10px;">Очистить</button>' +
'<button class="calculate-btn">Рассчитать</button>' +
"</div>";
// Обработчики событий
calculatorInterface
.querySelector(".clear-btn")
.addEventListener("click", function () {
LawCalculatorModule.clearSelection();
});
calculatorInterface
.querySelector(".calculate-btn")
.addEventListener("click", function () {
LawCalculatorModule.calculateSentence();
});
return calculatorInterface;
},
makeTableInteractive: function (table) {
var cells = table.querySelectorAll("td");
cells.forEach(
function (cell) {
var link = cell.querySelector('a[href*="#"]');
if (!link) return;
var href = link.getAttribute("href");
var match = href.match(/#(\d{3})/);
if (!match) return;
var lawCode = match[1];
if (!this.lawData[lawCode]) return;
cell.classList.add("law-cell-calculator");
cell.dataset.lawCode = lawCode;
// Сохраняем оригинальный обработчик клика
var originalClickHandler = cell.onclick;
cell.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
LawCalculatorModule.toggleViolation(lawCode, cell);
});
}.bind(this),
);
},
makeTableNormal: function (table) {
var cells = table.querySelectorAll(".law-cell-calculator");
cells.forEach(function (cell) {
cell.classList.remove("law-cell-calculator", "selected");
// Восстанавливаем оригинальную функциональность навигации
var lawCode = cell.dataset.lawCode;
if (lawCode) {
cell.addEventListener("click", function (e) {
e.preventDefault();
window.location.hash = lawCode;
});
}
});
},
toggleViolation: function (lawCode, cell) {
var index = this.selectedViolations.indexOf(lawCode);
if (index > -1) {
// Убираем нарушение
this.selectedViolations.splice(index, 1);
cell.classList.remove("selected");
} else {
// Добавляем нарушение
this.selectedViolations.push(lawCode);
cell.classList.add("selected");
}
this.updateViolationsList();
},
updateViolationsList: function () {
var violationsList = document.querySelector(".violations-list");
if (!violationsList) return;
violationsList.innerHTML = "";
this.selectedViolations.forEach(
function (lawCode) {
var law = this.lawData[lawCode];
if (!law) return;
var item = document.createElement("div");
item.className = "violation-item";
item.innerHTML =
lawCode +
" - " +
law.title +
'<span class="remove-btn" data-law-code="' +
lawCode +
'">×</span>';
item.querySelector(".remove-btn").addEventListener(
"click",
function () {
LawCalculatorModule.removeViolation(lawCode);
},
);
violationsList.appendChild(item);
}.bind(this),
);
},
removeViolation: function (lawCode) {
var index = this.selectedViolations.indexOf(lawCode);
if (index > -1) {
this.selectedViolations.splice(index, 1);
// Убираем выделение с ячейки
var cell = document.querySelector(
'[data-law-code="' + lawCode + '"]',
);
if (cell) {
cell.classList.remove("selected");
}
this.updateViolationsList();
}
},
clearSelection: function () {
this.selectedViolations = [];
// Убираем выделение со всех ячеек
var selectedCells = document.querySelectorAll(
".law-cell-calculator.selected",
);
selectedCells.forEach(function (cell) {
cell.classList.remove("selected");
});
this.updateViolationsList();
this.hideResult();
},
calculateSentence: function () {
if (this.selectedViolations.length === 0) {
alert("Выберите хотя бы одно нарушение");
return;
}
var result = this.calculateSentenceLogic(this.selectedViolations);
this.showResult(result);
},
calculateSentenceLogic: function (violations) {
// Группируем нарушения по категориям
var categories = {};
violations.forEach(
function (lawCode) {
var law = this.lawData[lawCode];
if (!law || !law.category) return;
// Используем номер статьи как ключ категории
var categoryKey = this.getCategoryKeyByLawCode(lawCode);
if (!categories[categoryKey]) {
categories[categoryKey] = [];
}
categories[categoryKey].push(law);
}.bind(this),
);
var totalMinTime = 0;
var totalMaxTime = 0;
var totalFine = 0;
var hasCritical = false;
var resultText = "";
// Обрабатываем каждую категорию
Object.keys(categories).forEach(
function (categoryKey) {
var laws = categories[categoryKey];
var category = laws[0].category;
var categoryName = this.getCategoryName(categoryKey);
if (category.isCritical) {
hasCritical = true;
resultText +=
"• " +
categoryName +
": " +
(category.punishment || "Перевод в Д-класс/казнь") +
"\n";
} else {
// Для одной категории берем самую тяжкую статью
var maxLaw = laws.reduce(function (max, current) {
var currentCode = parseInt(lawCode);
var maxCode = parseInt(max.title.match(/\d{3}/)[0]);
return currentCode > maxCode ? current : max;
});
totalMinTime += category.minTime || 0;
totalMaxTime += category.maxTime || 0;
totalFine += category.fine || 0;
var punishmentText = "";
if (category.punishment) {
punishmentText = category.punishment;
} else if (category.minTime !== undefined) {
punishmentText =
category.minTime === category.maxTime
? category.minTime + " минут"
: category.minTime +
"-" +
category.maxTime +
" минут";
}
resultText +=
"• " +
categoryName +
": " +
punishmentText +
" (статья " +
lawCode +
")\n";
}
}.bind(this),
);
if (hasCritical) {
return {
isCritical: true,
text:
"КРИТИЧЕСКИЕ НАРУШЕНИЯ\n" +
resultText +
"\nПриговор: Перевод в Д-класс или казнь",
};
} else if (this.pageType === "d-class") {
// Для Д-класса показываем штрафы и время отдельно
var summaryText = "";
if (totalFine > 0) {
summaryText += "Общий штраф: " + totalFine + " кредитов\n";
}
if (totalMinTime > 0 || totalMaxTime > 0) {
summaryText +=
"Общее время: " +
this.formatTime(totalMinTime, totalMaxTime) +
"\n";
}
return {
isCritical: false,
text:
"РАСЧЕТ НАКАЗАНИЙ Д-КЛАССА\n" +
resultText +
"\n" +
summaryText,
};
} else if (totalMinTime > 0 || totalMaxTime > 0) {
var timeText = this.formatTime(totalMinTime, totalMaxTime);
return {
isCritical: false,
text:
"РАСЧЕТ СРОКА ЗАКЛЮЧЕНИЯ\n" +
resultText +
"\nОбщий срок: " +
timeText,
};
} else {
return {
isCritical: false,
text:
"РАСЧЕТ НАКАЗАНИЙ\n" +
resultText +
"\nНе удалось определить наказания",
};
}
},
getCategoryKeyByLawCode: function (lawCode) {
var code = parseInt(lawCode);
if (code >= 100 && code <= 109) return "100-109";
else if (code >= 200 && code <= 211) return "200-211";
else if (code >= 300 && code <= 311) return "300-311";
else if (code >= 400 && code <= 411) return "400-411";
else if (code >= 500 && code <= 511) return "500-511";
return "unknown";
},
getCategoryName: function (categoryKey) {
var names = {
"100-109": "Лёгкие нарушения",
"200-211": "Средние нарушения",
"300-311": "Тяжкие нарушения",
"400-411": "Особо тяжкие нарушения",
"500-511": "Критические нарушения",
};
return names[categoryKey] || "Неизвестная категория";
},
formatTime: function (minTime, maxTime) {
if (minTime === maxTime) {
return this.formatMinutes(minTime);
} else {
return (
this.formatMinutes(minTime) +
" - " +
this.formatMinutes(maxTime)
);
}
},
formatMinutes: function (minutes) {
if (minutes < 60) {
return minutes + " минут";
} else {
var hours = Math.floor(minutes / 60);
var remainingMinutes = minutes % 60;
var result = hours + " час";
if (hours > 1 && hours < 5) result += "а";
if (hours >= 5) result += "ов";
if (remainingMinutes > 0) {
result += " " + remainingMinutes + " минут";
}
return result;
}
},
showResult: function (result) {
var resultDiv = document.querySelector(".calculation-result");
if (!resultDiv) return;
resultDiv.textContent = result.text;
resultDiv.className =
"calculation-result" + (result.isCritical ? " critical" : "");
resultDiv.style.display = "block";
},
hideResult: function () {
var resultDiv = document.querySelector(".calculation-result");
if (resultDiv) {
resultDiv.style.display = "none";
}
},
observeTables: function () {
// Наблюдаем за динамически добавляемыми таблицами
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
if (node.nodeType === 1) {
if (
node.classList &&
node.classList.contains("citizen-table-wrapper")
) {
setTimeout(function () {
LawCalculatorModule.createModeToggle();
}, 100);
} else if (node.querySelectorAll) {
var tables = node.querySelectorAll(
".citizen-table-wrapper",
);
if (tables.length > 0) {
setTimeout(function () {
LawCalculatorModule.createModeToggle();
}, 100);
}
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
},
};
var SnowflakesModule = {
snowflakes: [],
createInterval: null,
isActive: false,
snowflakeCount: 30,
random: function (min, max) {
return Math.random() * (max - min) + min;
},
init: function () {
if (this.isActive) return;
this.addStyles();
this.startContinuousCreation();
this.isActive = true;
},
addStyles: function () {
if (document.getElementById("snowflakes-styles")) return;
var style = document.createElement("style");
style.id = "snowflakes-styles";
style.textContent =
".snowflake {" +
"position: fixed;" +
"top: -10px;" +
"color: #fff;" +
"font-family: Arial, sans-serif;" +
"user-select: none;" +
"pointer-events: none;" +
"z-index: 1;" +
"animation-name: snowflake-fall;" +
"animation-timing-function: linear;" +
"animation-iteration-count: infinite;" +
"will-change: transform;" +
"}" +
"@keyframes snowflake-fall {" +
"0% {" +
"transform: translateY(0) translateX(0) rotate(0deg);" +
"}" +
"25% {" +
"transform: translateY(25vh) translateX(10px) rotate(90deg);" +
"}" +
"50% {" +
"transform: translateY(50vh) translateX(-10px) rotate(180deg);" +
"}" +
"75% {" +
"transform: translateY(75vh) translateX(10px) rotate(270deg);" +
"}" +
"100% {" +
"transform: translateY(calc(100vh + 100px)) translateX(0) rotate(360deg);" +
"}" +
"}" +
".snowflake.flare {" +
"text-shadow: 0 0 5px rgba(255, 255, 255, 0.4), 0 0 10px rgba(255, 255, 255, 0.2);" +
"}" +
".snowflake.blurred {" +
"filter: blur(2px);" +
"}";
document.head.appendChild(style);
},
createSnowflake: function () {
var snowflake = document.createElement("div");
snowflake.className = "snowflake";
var symbol = this.getSnowflakeSymbol();
snowflake.textContent = symbol;
var fontSize = this.random(0.5, 1.2);
snowflake.style.fontSize = fontSize + "em";
snowflake.style.left = this.random(0, 100) + "vw";
snowflake.style.opacity = this.random(0.25, 0.5);
var fallDuration = this.random(10, 30);
snowflake.style.animationDuration = fallDuration + "s";
snowflake.style.animationDelay = this.random(0, 2) + "s";
if (Math.random() > 0.95) {
snowflake.classList.add("flare");
}
if (Math.random() > 0.4) {
snowflake.classList.add("blurred");
}
document.body.appendChild(snowflake);
this.snowflakes.push(snowflake);
var self = this;
var checkRemove = setInterval(function () {
var rect = snowflake.getBoundingClientRect();
if (rect.top > window.innerHeight + 50) {
if (snowflake.parentNode) {
snowflake.remove();
}
var index = self.snowflakes.indexOf(snowflake);
if (index > -1) {
self.snowflakes.splice(index, 1);
}
clearInterval(checkRemove);
}
}, 100);
},
getSnowflakeSymbol: function () {
var symbols = ["❄", "❅", "❆"];
return symbols[Math.floor(Math.random() * symbols.length)];
},
startContinuousCreation: function () {
var self = this;
this.createInterval = setInterval(function () {
if (
self.isActive &&
self.snowflakes.length < self.snowflakeCount * 1.5
) {
self.createSnowflake();
}
}, 500);
},
stop: function () {
this.isActive = false;
if (this.createInterval) {
clearInterval(this.createInterval);
this.createInterval = null;
}
this.snowflakes.forEach(function (snowflake) {
if (snowflake && snowflake.parentNode) {
snowflake.remove();
}
});
this.snowflakes = [];
var styles = document.getElementById("snowflakes-styles");
if (styles) {
styles.remove();
}
},
toggle: function () {
if (this.isActive) {
this.stop();
} else {
this.init();
}
},
};
window.SnowflakesModule = SnowflakesModule;
var FireworksModule = {
fireworks: null,
container: null,
checkInterval: null,
isActive: false,
loaded: false,
loading: false,
callbacks: [],
init: function () {
this.startTimeCheck();
},
loadFireworks: function (callback) {
// Проверяем разные варианты экспорта библиотеки
if (
this.loaded &&
(window.Fireworks ||
(window.Fireworks && window.Fireworks.default))
) {
if (callback) callback();
return;
}
if (callback) {
this.callbacks.push(callback);
}
if (this.loading) return;
this.loading = true;
var self = this;
// Загружаем библиотеку fireworks.js через UMD версию
var script = document.createElement("script");
script.src = "https://unpkg.com/fireworks-js@2/dist/index.umd.js";
script.onload = function () {
self.loaded = true;
self.loading = false;
// Вызываем все колбэки
self.callbacks.forEach(function (cb) {
cb();
});
self.callbacks = [];
};
script.onerror = function () {
console.error("Ошибка загрузки fireworks.js");
self.loading = false;
self.callbacks = [];
};
document.head.appendChild(script);
},
getMoscowTime: function () {
// МСК = UTC+3
var now = new Date();
var utc = now.getTime() + now.getTimezoneOffset() * 60000;
var moscowTime = new Date(utc + 3 * 3600000); // UTC+3
return moscowTime;
},
isFireworksTime: function () {
var moscowTime = this.getMoscowTime();
var hours = moscowTime.getHours();
// С 00:00 до 01:00 по МСК
return hours === 0;
},
startTimeCheck: function () {
var self = this;
// Проверяем сразу при загрузке
this.checkTime();
// Проверяем каждую минуту
this.checkInterval = setInterval(function () {
self.checkTime();
}, 60000);
},
checkTime: function () {
if (this.isFireworksTime()) {
if (!this.isActive) {
this.startFireworks();
}
} else {
if (this.isActive) {
this.stopFireworks();
}
}
},
getFireworksConstructor: function () {
// Пробуем разные варианты доступа к конструктору
if (window.Fireworks && typeof window.Fireworks === "function") {
return window.Fireworks;
}
if (
window.Fireworks &&
window.Fireworks.default &&
typeof window.Fireworks.default === "function"
) {
return window.Fireworks.default;
}
if (window.fireworks && typeof window.fireworks === "function") {
return window.fireworks;
}
// Если библиотека экспортируется по-другому
if (
typeof Fireworks !== "undefined" &&
typeof Fireworks === "function"
) {
return Fireworks;
}
return null;
},
startFireworks: function () {
var self = this;
this.loadFireworks(function () {
if (!self.container) {
self.container = document.createElement("div");
self.container.id = "fireworks-container";
self.container.style.cssText =
"position: fixed;" +
"top: 0;" +
"left: 0;" +
"width: 100%;" +
"height: 100%;" +
"pointer-events: none;" +
"z-index: 99998;";
document.body.appendChild(self.container);
}
var FireworksConstructor = self.getFireworksConstructor();
if (!FireworksConstructor) {
console.error(
"Не удалось найти конструктор Fireworks. Проверьте загрузку библиотеки.",
);
console.log("Доступные объекты:", {
"window.Fireworks": window.Fireworks,
"window.Fireworks.default":
window.Fireworks && window.Fireworks.default,
"window.fireworks": window.fireworks,
});
return;
}
if (!self.fireworks) {
try {
self.fireworks = new FireworksConstructor(
self.container,
{
autoresize: true,
opacity: 0.5,
// Замедляем фейерверки
acceleration: 1.02, // Уменьшено с 1.05 для более медленного движения
friction: 0.98, // Увеличено с 0.97 для большего сопротивления
gravity: 1.2, // Уменьшено с 1.5 для более медленного падения
particles: 50,
traceLength: 5, // Увеличено с 3 для более длинного следа
traceSpeed: 5, // Уменьшено с 10 для более медленного следа
explosion: 5,
intensity: 30,
flickering: 50,
lineStyle: "round",
hue: { min: 0, max: 360 },
// Увеличиваем задержку между запусками для более медленного темпа
delay: { min: 30, max: 60 }, // Увеличено с 15-30 до 30-60
rocketsPoint: { min: 50, max: 50 },
lineWidth: {
explosion: { min: 1, max: 3 },
trace: { min: 1, max: 2 },
},
brightness: { min: 50, max: 80 },
decay: { min: 0.01, max: 0.02 }, // Уменьшено для более медленного затухания
mouse: { click: false, move: false, max: 1 },
// Включаем звук
sound: {
enable: true,
files: [
"https://fireworks.js.org/sounds/explosion0.mp3",
"https://fireworks.js.org/sounds/explosion1.mp3",
"https://fireworks.js.org/sounds/explosion2.mp3",
],
volume: { min: 0.3, max: 0.6 }, // Громкость от 30% до 60%
},
},
);
} catch (e) {
console.error(
"Ошибка создания экземпляра Fireworks:",
e,
);
return;
}
}
if (self.fireworks) {
self.fireworks.start();
self.isActive = true;
}
});
},
stopFireworks: function () {
if (this.fireworks) {
this.fireworks.stop();
this.isActive = false;
}
},
toggleFireworks: function () {
if (this.isActive) {
this.stopFireworks();
} else {
this.startFireworks();
}
},
};
window.FireworksModule = FireworksModule;
function initAllModules() {
SidebarModule.init();
AccessTooltipsModule.init();
LawTooltipsModule.init();
DataTooltipsModule.init();
CopyTextModule.init();
DocumentAutoFillModule.init();
// SnowflakesModule.init()
// FireworksModule.init();
// LawCalculatorModule.init();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initAllModules);
} else {
initAllModules();
}
})();