MediaWiki:Common.js: различия между версиями
Mei Day (обсуждение | вклад) Нет описания правки Метка: отменено |
Mei Day (обсуждение | вклад) Нет описания правки Метка: отменено |
||
| Строка 340: | Строка 340: | ||
}); | }); | ||
}); | }); | ||
/* ---- Left-side TOC sidebar for article pages ---- */ | |||
/* Scans h2/h3/h4 headings in the page content and builds a fixed | |||
sidebar on the left. Highlights the current section on scroll. */ | |||
(function () { | |||
"use strict"; | |||
function buildTOC() { | |||
// Skip main page (has its own layout) | |||
if (document.getElementById("orbitalis-parallax-bg")) return; | |||
// Skip special/edit pages | |||
var body = document.body; | |||
if ( | |||
body.classList.contains("ns-special") || | |||
body.classList.contains("action-edit") || | |||
body.classList.contains("action-submit") || | |||
body.classList.contains("action-history") | |||
) | |||
return; | |||
// Don't create twice | |||
if (document.getElementById("orb-toc-sidebar")) return; | |||
// Collect headings — try multiple content selectors for skin compat | |||
var root = | |||
document.querySelector(".mw-parser-output") || | |||
document.getElementById("mw-content-text") || | |||
document.getElementById("bodyContent"); | |||
if (!root) return; | |||
var headings = root.querySelectorAll("h2, h3, h4"); | |||
if (!headings.length) return; | |||
var items = []; | |||
for (var i = 0; i < headings.length; i++) { | |||
var h = headings[i]; | |||
// Citizen skin wraps headline text in .mw-headline or uses id on heading | |||
var headline = h.querySelector(".mw-headline"); | |||
var text = headline | |||
? headline.textContent | |||
: h.textContent.replace(/\[edit[^\]]*\]/gi, "").trim(); | |||
if (!text) continue; | |||
var id = headline ? headline.id : h.id; | |||
if (!id) { | |||
// Generate an id if missing | |||
id = "orb-heading-" + i; | |||
if (headline) headline.id = id; | |||
else h.id = id; | |||
} | |||
var level = parseInt(h.tagName.charAt(1), 10); | |||
items.push({ id: id, text: text, level: level, el: h }); | |||
} | |||
if (items.length < 2) return; // Not enough sections | |||
// Build sidebar DOM | |||
var sidebar = document.createElement("nav"); | |||
sidebar.id = "orb-toc-sidebar"; | |||
sidebar.setAttribute("aria-label", "Table of Contents"); | |||
var title = document.createElement("div"); | |||
title.className = "orb-toc-title"; | |||
title.textContent = "Содержание"; | |||
sidebar.appendChild(title); | |||
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-level-" + item.level; | |||
li.setAttribute("data-target", item.id); | |||
var a = document.createElement("a"); | |||
a.href = "#" + item.id; | |||
a.textContent = item.text; | |||
li.appendChild(a); | |||
ul.appendChild(li); | |||
} | |||
sidebar.appendChild(ul); | |||
document.body.appendChild(sidebar); | |||
document.body.classList.add("has-orb-toc"); | |||
// --- Scroll-spy: highlight current section --- | |||
var tocItems = 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 scrollY = window.scrollY || window.pageYOffset; | |||
var current = -1; | |||
for (var s = 0; s < headingEls.length; s++) { | |||
if (headingEls[s].getBoundingClientRect().top <= 100) { | |||
current = s; | |||
} | |||
} | |||
for (var t = 0; t < tocItems.length; t++) { | |||
if (t === current) { | |||
tocItems[t].classList.add("orb-toc-active"); | |||
} else { | |||
tocItems[t].classList.remove("orb-toc-active"); | |||
} | |||
} | |||
}); | |||
} | |||
window.addEventListener("scroll", onScroll, { passive: true }); | |||
onScroll(); // initial highlight | |||
} | |||
// Run on multiple hooks for skin compat | |||
if (typeof mw !== "undefined" && mw.hook) { | |||
mw.hook("wikipage.content").add(buildTOC); | |||
} | |||
document.addEventListener("DOMContentLoaded", buildTOC); | |||
window.addEventListener("load", function () { | |||
setTimeout(buildTOC, 100); | |||
}); | |||
})(); | |||
Версия от 13:07, 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;
}
// Mark body so CSS can target parallax pages (e.g. left-side TOC)
document.body.classList.add("has-parallax");
// ---- 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);
});
});
});
/* ---- Left-side TOC sidebar for article pages ---- */
/* Scans h2/h3/h4 headings in the page content and builds a fixed
sidebar on the left. Highlights the current section on scroll. */
(function () {
"use strict";
function buildTOC() {
// Skip main page (has its own layout)
if (document.getElementById("orbitalis-parallax-bg")) return;
// Skip special/edit pages
var body = document.body;
if (
body.classList.contains("ns-special") ||
body.classList.contains("action-edit") ||
body.classList.contains("action-submit") ||
body.classList.contains("action-history")
)
return;
// Don't create twice
if (document.getElementById("orb-toc-sidebar")) return;
// Collect headings — try multiple content selectors for skin compat
var root =
document.querySelector(".mw-parser-output") ||
document.getElementById("mw-content-text") ||
document.getElementById("bodyContent");
if (!root) return;
var headings = root.querySelectorAll("h2, h3, h4");
if (!headings.length) return;
var items = [];
for (var i = 0; i < headings.length; i++) {
var h = headings[i];
// Citizen skin wraps headline text in .mw-headline or uses id on heading
var headline = h.querySelector(".mw-headline");
var text = headline
? headline.textContent
: h.textContent.replace(/\[edit[^\]]*\]/gi, "").trim();
if (!text) continue;
var id = headline ? headline.id : h.id;
if (!id) {
// Generate an id if missing
id = "orb-heading-" + i;
if (headline) headline.id = id;
else h.id = id;
}
var level = parseInt(h.tagName.charAt(1), 10);
items.push({ id: id, text: text, level: level, el: h });
}
if (items.length < 2) return; // Not enough sections
// Build sidebar DOM
var sidebar = document.createElement("nav");
sidebar.id = "orb-toc-sidebar";
sidebar.setAttribute("aria-label", "Table of Contents");
var title = document.createElement("div");
title.className = "orb-toc-title";
title.textContent = "Содержание";
sidebar.appendChild(title);
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-level-" + item.level;
li.setAttribute("data-target", item.id);
var a = document.createElement("a");
a.href = "#" + item.id;
a.textContent = item.text;
li.appendChild(a);
ul.appendChild(li);
}
sidebar.appendChild(ul);
document.body.appendChild(sidebar);
document.body.classList.add("has-orb-toc");
// --- Scroll-spy: highlight current section ---
var tocItems = 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 scrollY = window.scrollY || window.pageYOffset;
var current = -1;
for (var s = 0; s < headingEls.length; s++) {
if (headingEls[s].getBoundingClientRect().top <= 100) {
current = s;
}
}
for (var t = 0; t < tocItems.length; t++) {
if (t === current) {
tocItems[t].classList.add("orb-toc-active");
} else {
tocItems[t].classList.remove("orb-toc-active");
}
}
});
}
window.addEventListener("scroll", onScroll, { passive: true });
onScroll(); // initial highlight
}
// Run on multiple hooks for skin compat
if (typeof mw !== "undefined" && mw.hook) {
mw.hook("wikipage.content").add(buildTOC);
}
document.addEventListener("DOMContentLoaded", buildTOC);
window.addEventListener("load", function () {
setTimeout(buildTOC, 100);
});
})();