MediaWiki:Common.js: различия между версиями
Mei Day (обсуждение | вклад) Нет описания правки |
Mei Day (обсуждение | вклад) Нет описания правки |
||
| (не показано 30 промежуточных версий этого же участника) | |||
| Строка 1: | Строка 1: | ||
// Переключение вкладок | /* MediaWiki:Common.js — Orbitalis Wiki | ||
mw.hook( | * This code runs on every wiki page. | ||
var $buttons = $ | * Paste this into MediaWiki:Common.js on the wiki (requires admin rights). | ||
var $blocks | * | ||
* Parallax space background that follows the mouse cursor, | |||
* matching the in-game lobby style. | |||
*/ | |||
(function () { | |||
"use strict"; | |||
// Only activate on the main page (or everywhere — your choice) | |||
var parallaxBg = document.getElementById("orbitalis-parallax-bg"); | |||
if (!parallaxBg) return; // no parallax container on this page | |||
var nebulaEl = document.getElementById("parallax-nebula"); | |||
var starsSmallEl = document.getElementById("parallax-stars-small"); | |||
var starsBigEl = document.getElementById("parallax-stars-big"); | |||
// Disable parallax on diff/comparison and old revision pages | |||
var params = new URLSearchParams(window.location.search); | |||
if ( | |||
params.has("diff") || | |||
params.has("oldid") || | |||
params.get("action") === "historysubmit" | |||
) { | |||
var hideEls = [ | |||
parallaxBg, | |||
nebulaEl, | |||
starsSmallEl, | |||
starsBigEl, | |||
document.getElementById("orbitalis-vignette"), | |||
]; | |||
for (var h = 0; h < hideEls.length; h++) { | |||
if (hideEls[h]) hideEls[h].style.display = "none"; | |||
} | |||
// Restore opaque background so diff/old revision is readable | |||
document.body.style.background = "#060608"; | |||
var contentEl = document.getElementById("content"); | |||
if (contentEl) { | |||
contentEl.style.background = "#14141a"; | |||
} | |||
return; | |||
} | |||
// ---- Apply styles to the parallax elements ---------------------------- | |||
// We do it from JS because MediaWiki strips complex inline styles | |||
// Background container | |||
parallaxBg.style.cssText = | |||
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:-10;pointer-events:none;overflow:hidden;background:#060608;"; | |||
// Shared layer base | |||
var layerBase = | |||
"position:fixed;top:-50px;left:-50px;width:calc(100% + 100px);height:calc(100% + 100px);pointer-events:none;"; | |||
// Nebula | |||
if (nebulaEl) { | |||
nebulaEl.style.cssText = | |||
layerBase + | |||
"background:" + | |||
"radial-gradient(ellipse at 70% 40%, rgba(30,50,80,0.4) 0%, transparent 50%)," + | |||
"radial-gradient(ellipse at 85% 60%, rgba(50,30,60,0.3) 0%, transparent 45%)," + | |||
"radial-gradient(ellipse at 60% 70%, rgba(20,40,70,0.35) 0%, transparent 55%);" + | |||
"opacity:0.7;z-index:-9;"; | |||
} | |||
// Small stars | |||
if (starsSmallEl) { | |||
starsSmallEl.style.cssText = | |||
layerBase + | |||
"background-image:" + | |||
"radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.8), transparent)," + | |||
"radial-gradient(1px 1px at 25% 35%, rgba(255,255,255,0.6), transparent)," + | |||
"radial-gradient(1px 1px at 40% 10%, rgba(255,255,255,0.7), transparent)," + | |||
"radial-gradient(1px 1px at 55% 45%, rgba(255,255,255,0.5), transparent)," + | |||
"radial-gradient(1px 1px at 70% 25%, rgba(255,255,255,0.8), transparent)," + | |||
"radial-gradient(1px 1px at 85% 55%, rgba(255,255,255,0.6), transparent)," + | |||
"radial-gradient(1px 1px at 15% 60%, rgba(255,255,255,0.7), transparent)," + | |||
"radial-gradient(1px 1px at 30% 75%, rgba(255,255,255,0.5), transparent)," + | |||
"radial-gradient(1px 1px at 45% 85%, rgba(255,255,255,0.8), transparent)," + | |||
"radial-gradient(1px 1px at 60% 70%, rgba(255,255,255,0.6), transparent)," + | |||
"radial-gradient(1px 1px at 75% 90%, rgba(255,255,255,0.7), transparent)," + | |||
"radial-gradient(1px 1px at 90% 15%, rgba(255,255,255,0.5), transparent)," + | |||
"radial-gradient(1px 1px at 5% 40%, rgba(255,255,255,0.6), transparent)," + | |||
"radial-gradient(1px 1px at 20% 95%, rgba(255,255,255,0.7), transparent)," + | |||
"radial-gradient(1px 1px at 35% 50%, rgba(255,255,255,0.5), transparent)," + | |||
"radial-gradient(1px 1px at 50% 30%, rgba(255,255,255,0.8), transparent)," + | |||
"radial-gradient(1px 1px at 65% 5%, rgba(255,255,255,0.6), transparent)," + | |||
"radial-gradient(1px 1px at 80% 65%, rgba(255,255,255,0.7), transparent)," + | |||
"radial-gradient(1px 1px at 95% 80%, rgba(255,255,255,0.5), transparent)," + | |||
"radial-gradient(1px 1px at 12% 88%, rgba(255,255,255,0.6), transparent);" + | |||
"background-size:200px 200px;opacity:0.8;z-index:-8;"; | |||
} | |||
// Big stars | |||
if (starsBigEl) { | |||
starsBigEl.style.cssText = | |||
layerBase + | |||
"background-image:" + | |||
"radial-gradient(2px 2px at 8% 15%, rgba(200,220,255,0.9), transparent)," + | |||
"radial-gradient(2px 2px at 22% 42%, rgba(255,240,220,0.8), transparent)," + | |||
"radial-gradient(2px 2px at 38% 8%, rgba(220,255,255,0.7), transparent)," + | |||
"radial-gradient(2px 2px at 52% 68%, rgba(255,255,220,0.8), transparent)," + | |||
"radial-gradient(2px 2px at 68% 32%, rgba(200,200,255,0.9), transparent)," + | |||
"radial-gradient(2px 2px at 82% 78%, rgba(255,220,200,0.7), transparent)," + | |||
"radial-gradient(2px 2px at 18% 55%, rgba(220,255,220,0.8), transparent)," + | |||
"radial-gradient(2px 2px at 48% 92%, rgba(255,200,255,0.7), transparent)," + | |||
"radial-gradient(2px 2px at 72% 18%, rgba(200,255,255,0.8), transparent)," + | |||
"radial-gradient(2px 2px at 92% 45%, rgba(255,255,200,0.7), transparent);" + | |||
"background-size:300px 300px;opacity:0.6;z-index:-7;"; | |||
} | |||
// Vignette overlay | |||
var vignetteEl = document.getElementById("orbitalis-vignette"); | |||
if (vignetteEl) { | |||
vignetteEl.style.cssText = | |||
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:-6;pointer-events:none;" + | |||
"background:radial-gradient(ellipse at center, rgba(6,6,8,0) 30%, rgba(6,6,8,0.6) 70%, #060608 100%);"; | |||
} | |||
// ---- Override MediaWiki default backgrounds ---------------------------- | |||
document.body.style.background = "transparent"; | |||
var ids = ["mw-page-base", "mw-head-base"]; | |||
for (var i = 0; i < ids.length; i++) { | |||
var el = document.getElementById(ids[i]); | |||
if (el) el.style.background = "none"; | |||
} | |||
var content = document.getElementById("content"); | |||
if (content) { | |||
content.style.background = "none"; | |||
content.style.border = "none"; | |||
} | |||
var bodyContent = document.getElementById("bodyContent"); | |||
if (bodyContent) { | |||
bodyContent.style.position = "relative"; | |||
} | |||
// ---- Mouse-tracking parallax animation -------------------------------- | |||
var targetX = 0, | |||
targetY = 0; | |||
var currentX = 0, | |||
currentY = 0; | |||
document.addEventListener("mousemove", function (e) { | |||
var w = window.innerWidth || document.documentElement.clientWidth; | |||
var h = window.innerHeight || document.documentElement.clientHeight; | |||
targetX = (e.clientX / w - 0.5) * 2; | |||
targetY = (e.clientY / h - 0.5) * 2; | |||
}); | |||
function animate() { | |||
currentX += (targetX - currentX) * 0.06; | |||
currentY += (targetY - currentY) * 0.06; | |||
if (nebulaEl) { | |||
nebulaEl.style.left = currentX * 12 - 50 + "px"; | |||
nebulaEl.style.top = currentY * 12 - 50 + "px"; | |||
} | |||
if (starsSmallEl) { | |||
starsSmallEl.style.left = currentX * 25 - 50 + "px"; | |||
starsSmallEl.style.top = currentY * 25 - 50 + "px"; | |||
} | |||
if (starsBigEl) { | |||
starsBigEl.style.left = currentX * 40 - 50 + "px"; | |||
starsBigEl.style.top = currentY * 40 - 50 + "px"; | |||
} | |||
requestAnimationFrame(animate); | |||
} | |||
animate(); | |||
})(); | |||
/* ---- Server online status + info ---- */ | |||
/* Queries /api/status.php on the same wiki host — which sends a UDP BYOND | |||
* topic to localhost:41060 and returns JSON { players, map_name, chaos_level, ... }. | |||
* No proxy needed: wiki and game server share the same machine. | |||
*/ | |||
(function () { | |||
"use strict"; | |||
var elOnline = document.getElementById("orb-online"); | |||
var elMap = document.getElementById("orb-map-val"); | |||
var elST = document.getElementById("orb-st-val"); | |||
if (!elOnline) return; | |||
var STATUS_URL = "/api/status.json"; | |||
var REFRESH_MS = 30000; | |||
function update() { | |||
fetch(STATUS_URL) | |||
.then(function (r) { | |||
console.log("[Orbitalis Status] HTTP", r.status, r.url); | |||
if (!r.ok) throw new Error("HTTP " + r.status); | |||
return r.json(); | |||
}) | |||
.then(function (data) { | |||
console.log("[Orbitalis Status] Data:", data); | |||
var count = parseInt(data.players, 10); | |||
if (isNaN(count)) throw new Error("bad data: players=" + data.players); | |||
elOnline.textContent = count + " онлайн"; | |||
elOnline.style.color = | |||
count > 0 ? "rgba(100,220,180,0.75)" : "rgba(100,220,180,0.35)"; | |||
elOnline.style.textShadow = | |||
count > 0 ? "0 0 15px rgba(100,220,180,0.2)" : "none"; | |||
if (elMap && data.map_name) { | |||
elMap.textContent = data.map_name; | |||
elMap.style.color = "rgba(255,255,255,0.55)"; | |||
} | |||
if (elST && data.chaos_level) { | |||
elST.textContent = data.chaos_level; | |||
elST.style.color = "rgba(255,255,255,0.55)"; | |||
} | |||
}) | |||
.catch(function (err) { | |||
console.error("[Orbitalis Status] Fetch failed:", err); | |||
elOnline.textContent = "оффлайн"; | |||
elOnline.style.color = "rgba(255,120,120,0.5)"; | |||
elOnline.style.textShadow = "none"; | |||
if (elMap) elMap.textContent = "—"; | |||
if (elST) elST.textContent = "—"; | |||
}); | |||
} | |||
update(); | |||
setInterval(update, REFRESH_MS); | |||
})(); | |||
/* ---- Main page: interactive engine ---- */ | |||
/* All visual styling is in Common.css (CSS-first approach). | |||
JS handles: nuclear strip for <a> tags, click handlers. | |||
Safe to re-run on Citizen DOM updates. */ | |||
(function () { | |||
"use strict"; | |||
function run() { | |||
var orb = document.getElementById("orb-content"); | |||
if (!orb) return; | |||
/* Nuclear strip: clear Citizen-applied backgrounds on <a> tags only */ | |||
var links = orb.querySelectorAll("a"); | |||
for (var i = 0; i < links.length; i++) { | |||
var a = links[i]; | |||
a.style.setProperty("background", "none", "important"); | |||
a.style.setProperty("background-color", "transparent", "important"); | |||
a.style.setProperty("background-image", "none", "important"); | |||
a.style.setProperty("box-shadow", "none", "important"); | |||
a.style.setProperty("border", "none", "important"); | |||
a.style.setProperty("padding", "0", "important"); | |||
a.style.setProperty("text-decoration", "none", "important"); | |||
} | |||
/* Connect button — byond:// link */ | |||
var addr = document.getElementById("orb-addr"); | |||
if (addr && !addr._orbClick) { | |||
addr._orbClick = true; | |||
addr.addEventListener("click", function () { | |||
window.location.href = "byond://45.141.208.222:41060"; | |||
}); | |||
} | |||
/* Make entire card area clickable */ | |||
var cardIds = [ | |||
"orb-card-1", | |||
"orb-card-2", | |||
"orb-card-3", | |||
"orb-card-4", | |||
"orb-card-5", | |||
"orb-card-6", | |||
"orb-card-7", | |||
"orb-card-8", | |||
]; | |||
for (var j = 0; j < cardIds.length; j++) { | |||
var card = document.getElementById(cardIds[j]); | |||
if (card && !card._orbClick) { | |||
card._orbClick = true; | |||
(function (c) { | |||
var link = c.querySelector("a"); | |||
if (link) { | |||
c.addEventListener("click", function (ev) { | |||
if ( | |||
ev.target.tagName !== "A" && | |||
(!ev.target.parentNode || ev.target.parentNode.tagName !== "A") | |||
) { | |||
link.click(); | |||
} | |||
}); | |||
} | |||
})(card); | |||
} | |||
} | |||
} | |||
/* Hook into multiple events — retry for Citizen's late DOM updates */ | |||
if (typeof mw !== "undefined" && mw.hook) { | |||
mw.hook("wikipage.content").add(run); | |||
} | |||
document.addEventListener("DOMContentLoaded", run); | |||
window.addEventListener("load", function () { | |||
setTimeout(run, 50); | |||
setTimeout(run, 500); | |||
}); | |||
})(); | |||
// Переключение вкладок и классов "chaos-*". | |||
mw.hook("wikipage.content").add(function ($root) { | |||
$root.find(".hj-chaos-container").each(function () { | |||
var $container = $(this); | |||
var $buttons = $container.find(".hj-chaos-tab-button"); | |||
var $blocks = $container.find(".hj-chaos-block"); | |||
if (!$buttons.length || !$blocks.length) return; | if (!$buttons.length || !$blocks.length) return; | ||
$buttons. | var CHAOS_CLASSES = "chaos-overview chaos-calm chaos-medium chaos-high"; | ||
function activate(key, $btn) { | |||
// активная кнопка | |||
$buttons.removeClass("active"); | |||
$btn.addClass("active"); | |||
// активный блок | |||
$blocks.removeClass("active").hide(); | |||
$blocks | |||
.filter('[data-chaos="' + key + '"]') | |||
.addClass("active") | |||
.show(); | |||
// класс на контейнере для раскраски рамок/фона | |||
$container.removeClass(CHAOS_CLASSES).addClass("chaos-" + key); | |||
} | |||
// Инициализация | |||
var $activeBtn = $buttons.filter(".active").first(); | |||
if ($activeBtn.length) { | |||
activate($activeBtn.data("chaos"), $activeBtn); | |||
} else { | |||
var $first = $buttons.first(); | |||
activate($first.data("chaos"), $first); | |||
} | |||
// Клики | |||
$buttons.off("click.hjChaos").on("click.hjChaos", function () { | |||
var $btn = $(this); | |||
activate($btn.data("chaos"), $btn); | |||
}); | }); | ||
}); | |||
}); | }); | ||
/* ---- Orbitalis custom TOC ---- */ | |||
/* Activates when the page contains <div id="orb-toc"></div> (or class="orb-toc"). | |||
Creates #orb-toc-sidebar directly in document.body so position:fixed | |||
is never broken by skin containers with transform/will-change. */ | |||
(function () { | |||
"use strict"; | |||
function initOrbToc() { | |||
// Already built? | |||
if (document.getElementById("orb-toc-sidebar")) return; | |||
// Don't show in editor or other non-view actions | |||
if (document.body.classList.contains("action-edit") || | |||
document.body.classList.contains("action-submit") || | |||
document.body.classList.contains("action-history")) return; | |||
// Look for the opt-in marker | |||
var marker = | |||
document.getElementById("orb-toc") || document.querySelector(".orb-toc"); | |||
if (!marker) return; | |||
// Find content root | |||
var root = | |||
document.querySelector(".mw-parser-output") || | |||
document.getElementById("mw-content-text") || | |||
document.getElementById("bodyContent"); | |||
if (!root) return; | |||
// Scan ALL heading levels (h1 through h4) | |||
var headings = root.querySelectorAll("h1, h2, h3, h4"); | |||
if (!headings.length) return; | |||
var items = []; | |||
for (var i = 0; i < headings.length; i++) { | |||
var h = headings[i]; | |||
// Citizen skin: heading text in .mw-headline span | |||
var headline = h.querySelector(".mw-headline"); | |||
var text = headline | |||
? headline.textContent | |||
: h.textContent | |||
.replace(/\[\s*править[^\]]*\]/gi, "") | |||
.replace(/\[edit[^\]]*\]/gi, "") | |||
.trim(); | |||
if (!text) continue; | |||
var id = headline ? headline.id : h.id; | |||
if (!id) { | |||
id = "orb-h-" + i; | |||
if (headline) headline.id = id; | |||
else h.id = id; | |||
} | |||
var tag = h.tagName.toLowerCase(); | |||
items.push({ id: id, text: text, tag: tag, el: h }); | |||
} | |||
if (items.length < 2) return; | |||
// --- Create sidebar in document.body --- | |||
var sidebar = document.createElement("nav"); | |||
sidebar.id = "orb-toc-sidebar"; | |||
sidebar.setAttribute("aria-label", "Table of Contents"); | |||
var head = document.createElement("div"); | |||
head.className = "orb-toc-head"; | |||
head.textContent = | |||
"\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435"; | |||
sidebar.appendChild(head); | |||
var ul = document.createElement("ul"); | |||
for (var j = 0; j < items.length; j++) { | |||
var item = items[j]; | |||
var li = document.createElement("li"); | |||
li.className = "orb-toc-" + item.tag; | |||
li.setAttribute("data-target", item.id); | |||
var a = document.createElement("a"); | |||
a.href = "#" + item.id; | |||
a.textContent = item.text; | |||
(function (targetId) { | |||
a.addEventListener("click", function (e) { | |||
var target = document.getElementById(targetId); | |||
if (target) { | |||
e.preventDefault(); | |||
target.scrollIntoView({ behavior: "smooth", block: "start" }); | |||
history.replaceState(null, "", "#" + targetId); | |||
} | |||
}); | |||
})(item.id); | |||
li.appendChild(a); | |||
ul.appendChild(li); | |||
} | |||
sidebar.appendChild(ul); | |||
// Append to body - outside any Citizen container | |||
document.body.appendChild(sidebar); | |||
// --- Scroll spy --- | |||
var allLi = ul.querySelectorAll("li"); | |||
var headingEls = []; | |||
for (var k = 0; k < items.length; k++) { | |||
headingEls.push(items[k].el); | |||
} | |||
var ticking = false; | |||
function onScroll() { | |||
if (ticking) return; | |||
ticking = true; | |||
requestAnimationFrame(function () { | |||
ticking = false; | |||
var current = -1; | |||
for (var s = 0; s < headingEls.length; s++) { | |||
if (headingEls[s].getBoundingClientRect().top <= 120) { | |||
current = s; | |||
} | |||
} | |||
for (var t = 0; t < allLi.length; t++) { | |||
if (t === current) { | |||
allLi[t].classList.add("orb-toc-active"); | |||
} else { | |||
allLi[t].classList.remove("orb-toc-active"); | |||
} | |||
} | |||
}); | |||
} | |||
window.addEventListener("scroll", onScroll, { passive: true }); | |||
onScroll(); | |||
} | |||
// Run on multiple hooks for skin compat | |||
if (typeof mw !== "undefined" && mw.hook) { | |||
mw.hook("wikipage.content").add(initOrbToc); | |||
} | |||
document.addEventListener("DOMContentLoaded", initOrbToc); | |||
window.addEventListener("load", function () { | |||
setTimeout(initOrbToc, 150); | |||
}); | |||
})(); | |||
Текущая версия от 16:17, 10 апреля 2026
/* MediaWiki:Common.js — Orbitalis Wiki
* This code runs on every wiki page.
* Paste this into MediaWiki:Common.js on the wiki (requires admin rights).
*
* Parallax space background that follows the mouse cursor,
* matching the in-game lobby style.
*/
(function () {
"use strict";
// Only activate on the main page (or everywhere — your choice)
var parallaxBg = document.getElementById("orbitalis-parallax-bg");
if (!parallaxBg) return; // no parallax container on this page
var nebulaEl = document.getElementById("parallax-nebula");
var starsSmallEl = document.getElementById("parallax-stars-small");
var starsBigEl = document.getElementById("parallax-stars-big");
// Disable parallax on diff/comparison and old revision pages
var params = new URLSearchParams(window.location.search);
if (
params.has("diff") ||
params.has("oldid") ||
params.get("action") === "historysubmit"
) {
var hideEls = [
parallaxBg,
nebulaEl,
starsSmallEl,
starsBigEl,
document.getElementById("orbitalis-vignette"),
];
for (var h = 0; h < hideEls.length; h++) {
if (hideEls[h]) hideEls[h].style.display = "none";
}
// Restore opaque background so diff/old revision is readable
document.body.style.background = "#060608";
var contentEl = document.getElementById("content");
if (contentEl) {
contentEl.style.background = "#14141a";
}
return;
}
// ---- Apply styles to the parallax elements ----------------------------
// We do it from JS because MediaWiki strips complex inline styles
// Background container
parallaxBg.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:-10;pointer-events:none;overflow:hidden;background:#060608;";
// Shared layer base
var layerBase =
"position:fixed;top:-50px;left:-50px;width:calc(100% + 100px);height:calc(100% + 100px);pointer-events:none;";
// Nebula
if (nebulaEl) {
nebulaEl.style.cssText =
layerBase +
"background:" +
"radial-gradient(ellipse at 70% 40%, rgba(30,50,80,0.4) 0%, transparent 50%)," +
"radial-gradient(ellipse at 85% 60%, rgba(50,30,60,0.3) 0%, transparent 45%)," +
"radial-gradient(ellipse at 60% 70%, rgba(20,40,70,0.35) 0%, transparent 55%);" +
"opacity:0.7;z-index:-9;";
}
// Small stars
if (starsSmallEl) {
starsSmallEl.style.cssText =
layerBase +
"background-image:" +
"radial-gradient(1px 1px at 10% 20%, rgba(255,255,255,0.8), transparent)," +
"radial-gradient(1px 1px at 25% 35%, rgba(255,255,255,0.6), transparent)," +
"radial-gradient(1px 1px at 40% 10%, rgba(255,255,255,0.7), transparent)," +
"radial-gradient(1px 1px at 55% 45%, rgba(255,255,255,0.5), transparent)," +
"radial-gradient(1px 1px at 70% 25%, rgba(255,255,255,0.8), transparent)," +
"radial-gradient(1px 1px at 85% 55%, rgba(255,255,255,0.6), transparent)," +
"radial-gradient(1px 1px at 15% 60%, rgba(255,255,255,0.7), transparent)," +
"radial-gradient(1px 1px at 30% 75%, rgba(255,255,255,0.5), transparent)," +
"radial-gradient(1px 1px at 45% 85%, rgba(255,255,255,0.8), transparent)," +
"radial-gradient(1px 1px at 60% 70%, rgba(255,255,255,0.6), transparent)," +
"radial-gradient(1px 1px at 75% 90%, rgba(255,255,255,0.7), transparent)," +
"radial-gradient(1px 1px at 90% 15%, rgba(255,255,255,0.5), transparent)," +
"radial-gradient(1px 1px at 5% 40%, rgba(255,255,255,0.6), transparent)," +
"radial-gradient(1px 1px at 20% 95%, rgba(255,255,255,0.7), transparent)," +
"radial-gradient(1px 1px at 35% 50%, rgba(255,255,255,0.5), transparent)," +
"radial-gradient(1px 1px at 50% 30%, rgba(255,255,255,0.8), transparent)," +
"radial-gradient(1px 1px at 65% 5%, rgba(255,255,255,0.6), transparent)," +
"radial-gradient(1px 1px at 80% 65%, rgba(255,255,255,0.7), transparent)," +
"radial-gradient(1px 1px at 95% 80%, rgba(255,255,255,0.5), transparent)," +
"radial-gradient(1px 1px at 12% 88%, rgba(255,255,255,0.6), transparent);" +
"background-size:200px 200px;opacity:0.8;z-index:-8;";
}
// Big stars
if (starsBigEl) {
starsBigEl.style.cssText =
layerBase +
"background-image:" +
"radial-gradient(2px 2px at 8% 15%, rgba(200,220,255,0.9), transparent)," +
"radial-gradient(2px 2px at 22% 42%, rgba(255,240,220,0.8), transparent)," +
"radial-gradient(2px 2px at 38% 8%, rgba(220,255,255,0.7), transparent)," +
"radial-gradient(2px 2px at 52% 68%, rgba(255,255,220,0.8), transparent)," +
"radial-gradient(2px 2px at 68% 32%, rgba(200,200,255,0.9), transparent)," +
"radial-gradient(2px 2px at 82% 78%, rgba(255,220,200,0.7), transparent)," +
"radial-gradient(2px 2px at 18% 55%, rgba(220,255,220,0.8), transparent)," +
"radial-gradient(2px 2px at 48% 92%, rgba(255,200,255,0.7), transparent)," +
"radial-gradient(2px 2px at 72% 18%, rgba(200,255,255,0.8), transparent)," +
"radial-gradient(2px 2px at 92% 45%, rgba(255,255,200,0.7), transparent);" +
"background-size:300px 300px;opacity:0.6;z-index:-7;";
}
// Vignette overlay
var vignetteEl = document.getElementById("orbitalis-vignette");
if (vignetteEl) {
vignetteEl.style.cssText =
"position:fixed;top:0;left:0;width:100%;height:100%;z-index:-6;pointer-events:none;" +
"background:radial-gradient(ellipse at center, rgba(6,6,8,0) 30%, rgba(6,6,8,0.6) 70%, #060608 100%);";
}
// ---- Override MediaWiki default backgrounds ----------------------------
document.body.style.background = "transparent";
var ids = ["mw-page-base", "mw-head-base"];
for (var i = 0; i < ids.length; i++) {
var el = document.getElementById(ids[i]);
if (el) el.style.background = "none";
}
var content = document.getElementById("content");
if (content) {
content.style.background = "none";
content.style.border = "none";
}
var bodyContent = document.getElementById("bodyContent");
if (bodyContent) {
bodyContent.style.position = "relative";
}
// ---- Mouse-tracking parallax animation --------------------------------
var targetX = 0,
targetY = 0;
var currentX = 0,
currentY = 0;
document.addEventListener("mousemove", function (e) {
var w = window.innerWidth || document.documentElement.clientWidth;
var h = window.innerHeight || document.documentElement.clientHeight;
targetX = (e.clientX / w - 0.5) * 2;
targetY = (e.clientY / h - 0.5) * 2;
});
function animate() {
currentX += (targetX - currentX) * 0.06;
currentY += (targetY - currentY) * 0.06;
if (nebulaEl) {
nebulaEl.style.left = currentX * 12 - 50 + "px";
nebulaEl.style.top = currentY * 12 - 50 + "px";
}
if (starsSmallEl) {
starsSmallEl.style.left = currentX * 25 - 50 + "px";
starsSmallEl.style.top = currentY * 25 - 50 + "px";
}
if (starsBigEl) {
starsBigEl.style.left = currentX * 40 - 50 + "px";
starsBigEl.style.top = currentY * 40 - 50 + "px";
}
requestAnimationFrame(animate);
}
animate();
})();
/* ---- Server online status + info ---- */
/* Queries /api/status.php on the same wiki host — which sends a UDP BYOND
* topic to localhost:41060 and returns JSON { players, map_name, chaos_level, ... }.
* No proxy needed: wiki and game server share the same machine.
*/
(function () {
"use strict";
var elOnline = document.getElementById("orb-online");
var elMap = document.getElementById("orb-map-val");
var elST = document.getElementById("orb-st-val");
if (!elOnline) return;
var STATUS_URL = "/api/status.json";
var REFRESH_MS = 30000;
function update() {
fetch(STATUS_URL)
.then(function (r) {
console.log("[Orbitalis Status] HTTP", r.status, r.url);
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (data) {
console.log("[Orbitalis Status] Data:", data);
var count = parseInt(data.players, 10);
if (isNaN(count)) throw new Error("bad data: players=" + data.players);
elOnline.textContent = count + " онлайн";
elOnline.style.color =
count > 0 ? "rgba(100,220,180,0.75)" : "rgba(100,220,180,0.35)";
elOnline.style.textShadow =
count > 0 ? "0 0 15px rgba(100,220,180,0.2)" : "none";
if (elMap && data.map_name) {
elMap.textContent = data.map_name;
elMap.style.color = "rgba(255,255,255,0.55)";
}
if (elST && data.chaos_level) {
elST.textContent = data.chaos_level;
elST.style.color = "rgba(255,255,255,0.55)";
}
})
.catch(function (err) {
console.error("[Orbitalis Status] Fetch failed:", err);
elOnline.textContent = "оффлайн";
elOnline.style.color = "rgba(255,120,120,0.5)";
elOnline.style.textShadow = "none";
if (elMap) elMap.textContent = "—";
if (elST) elST.textContent = "—";
});
}
update();
setInterval(update, REFRESH_MS);
})();
/* ---- Main page: interactive engine ---- */
/* All visual styling is in Common.css (CSS-first approach).
JS handles: nuclear strip for <a> tags, click handlers.
Safe to re-run on Citizen DOM updates. */
(function () {
"use strict";
function run() {
var orb = document.getElementById("orb-content");
if (!orb) return;
/* Nuclear strip: clear Citizen-applied backgrounds on <a> tags only */
var links = orb.querySelectorAll("a");
for (var i = 0; i < links.length; i++) {
var a = links[i];
a.style.setProperty("background", "none", "important");
a.style.setProperty("background-color", "transparent", "important");
a.style.setProperty("background-image", "none", "important");
a.style.setProperty("box-shadow", "none", "important");
a.style.setProperty("border", "none", "important");
a.style.setProperty("padding", "0", "important");
a.style.setProperty("text-decoration", "none", "important");
}
/* Connect button — byond:// link */
var addr = document.getElementById("orb-addr");
if (addr && !addr._orbClick) {
addr._orbClick = true;
addr.addEventListener("click", function () {
window.location.href = "byond://45.141.208.222:41060";
});
}
/* Make entire card area clickable */
var cardIds = [
"orb-card-1",
"orb-card-2",
"orb-card-3",
"orb-card-4",
"orb-card-5",
"orb-card-6",
"orb-card-7",
"orb-card-8",
];
for (var j = 0; j < cardIds.length; j++) {
var card = document.getElementById(cardIds[j]);
if (card && !card._orbClick) {
card._orbClick = true;
(function (c) {
var link = c.querySelector("a");
if (link) {
c.addEventListener("click", function (ev) {
if (
ev.target.tagName !== "A" &&
(!ev.target.parentNode || ev.target.parentNode.tagName !== "A")
) {
link.click();
}
});
}
})(card);
}
}
}
/* Hook into multiple events — retry for Citizen's late DOM updates */
if (typeof mw !== "undefined" && mw.hook) {
mw.hook("wikipage.content").add(run);
}
document.addEventListener("DOMContentLoaded", run);
window.addEventListener("load", function () {
setTimeout(run, 50);
setTimeout(run, 500);
});
})();
// Переключение вкладок и классов "chaos-*".
mw.hook("wikipage.content").add(function ($root) {
$root.find(".hj-chaos-container").each(function () {
var $container = $(this);
var $buttons = $container.find(".hj-chaos-tab-button");
var $blocks = $container.find(".hj-chaos-block");
if (!$buttons.length || !$blocks.length) return;
var CHAOS_CLASSES = "chaos-overview chaos-calm chaos-medium chaos-high";
function activate(key, $btn) {
// активная кнопка
$buttons.removeClass("active");
$btn.addClass("active");
// активный блок
$blocks.removeClass("active").hide();
$blocks
.filter('[data-chaos="' + key + '"]')
.addClass("active")
.show();
// класс на контейнере для раскраски рамок/фона
$container.removeClass(CHAOS_CLASSES).addClass("chaos-" + key);
}
// Инициализация
var $activeBtn = $buttons.filter(".active").first();
if ($activeBtn.length) {
activate($activeBtn.data("chaos"), $activeBtn);
} else {
var $first = $buttons.first();
activate($first.data("chaos"), $first);
}
// Клики
$buttons.off("click.hjChaos").on("click.hjChaos", function () {
var $btn = $(this);
activate($btn.data("chaos"), $btn);
});
});
});
/* ---- Orbitalis custom TOC ---- */
/* Activates when the page contains <div id="orb-toc"></div> (or class="orb-toc").
Creates #orb-toc-sidebar directly in document.body so position:fixed
is never broken by skin containers with transform/will-change. */
(function () {
"use strict";
function initOrbToc() {
// Already built?
if (document.getElementById("orb-toc-sidebar")) return;
// Don't show in editor or other non-view actions
if (document.body.classList.contains("action-edit") ||
document.body.classList.contains("action-submit") ||
document.body.classList.contains("action-history")) return;
// Look for the opt-in marker
var marker =
document.getElementById("orb-toc") || document.querySelector(".orb-toc");
if (!marker) return;
// Find content root
var root =
document.querySelector(".mw-parser-output") ||
document.getElementById("mw-content-text") ||
document.getElementById("bodyContent");
if (!root) return;
// Scan ALL heading levels (h1 through h4)
var headings = root.querySelectorAll("h1, h2, h3, h4");
if (!headings.length) return;
var items = [];
for (var i = 0; i < headings.length; i++) {
var h = headings[i];
// Citizen skin: heading text in .mw-headline span
var headline = h.querySelector(".mw-headline");
var text = headline
? headline.textContent
: h.textContent
.replace(/\[\s*править[^\]]*\]/gi, "")
.replace(/\[edit[^\]]*\]/gi, "")
.trim();
if (!text) continue;
var id = headline ? headline.id : h.id;
if (!id) {
id = "orb-h-" + i;
if (headline) headline.id = id;
else h.id = id;
}
var tag = h.tagName.toLowerCase();
items.push({ id: id, text: text, tag: tag, el: h });
}
if (items.length < 2) return;
// --- Create sidebar in document.body ---
var sidebar = document.createElement("nav");
sidebar.id = "orb-toc-sidebar";
sidebar.setAttribute("aria-label", "Table of Contents");
var head = document.createElement("div");
head.className = "orb-toc-head";
head.textContent =
"\u0421\u043e\u0434\u0435\u0440\u0436\u0430\u043d\u0438\u0435";
sidebar.appendChild(head);
var ul = document.createElement("ul");
for (var j = 0; j < items.length; j++) {
var item = items[j];
var li = document.createElement("li");
li.className = "orb-toc-" + item.tag;
li.setAttribute("data-target", item.id);
var a = document.createElement("a");
a.href = "#" + item.id;
a.textContent = item.text;
(function (targetId) {
a.addEventListener("click", function (e) {
var target = document.getElementById(targetId);
if (target) {
e.preventDefault();
target.scrollIntoView({ behavior: "smooth", block: "start" });
history.replaceState(null, "", "#" + targetId);
}
});
})(item.id);
li.appendChild(a);
ul.appendChild(li);
}
sidebar.appendChild(ul);
// Append to body - outside any Citizen container
document.body.appendChild(sidebar);
// --- Scroll spy ---
var allLi = ul.querySelectorAll("li");
var headingEls = [];
for (var k = 0; k < items.length; k++) {
headingEls.push(items[k].el);
}
var ticking = false;
function onScroll() {
if (ticking) return;
ticking = true;
requestAnimationFrame(function () {
ticking = false;
var current = -1;
for (var s = 0; s < headingEls.length; s++) {
if (headingEls[s].getBoundingClientRect().top <= 120) {
current = s;
}
}
for (var t = 0; t < allLi.length; t++) {
if (t === current) {
allLi[t].classList.add("orb-toc-active");
} else {
allLi[t].classList.remove("orb-toc-active");
}
}
});
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
}
// Run on multiple hooks for skin compat
if (typeof mw !== "undefined" && mw.hook) {
mw.hook("wikipage.content").add(initOrbToc);
}
document.addEventListener("DOMContentLoaded", initOrbToc);
window.addEventListener("load", function () {
setTimeout(initOrbToc, 150);
});
})();