Перейти к содержанию

MediaWiki:Common.js: различия между версиями

Материал из Horny Jail WIKI
Новая страница: «Размещённый здесь код JavaScript будет загружаться пользователям при обращении к каждой странице: // Переключалка уровней хаоса на странице правил mw.hook('wikipage.content').add(function ($content) { var $tabs = $content.find('.hj-chaos-tabs'); if (!$tabs.length) return; $tabs.each(function () { var $container = $(this...»
 
Нет описания правки
 
(не показаны 32 промежуточные версии этого же участника)
Строка 1: Строка 1:
/* Размещённый здесь код JavaScript будет загружаться пользователям при обращении к каждой странице */
/* MediaWiki:Common.js — Orbitalis Wiki
// Переключалка уровней хаоса на странице правил
* This code runs on every wiki page.
mw.hook('wikipage.content').add(function ($content) {
* Paste this into MediaWiki:Common.js on the wiki (requires admin rights).
    var $tabs = $content.find('.hj-chaos-tabs');
*
    if (!$tabs.length) return;
* Parallax space background that follows the mouse cursor,
* matching the in-game lobby style.
*/
(function () {
  "use strict";


    $tabs.each(function () {
  // Only activate on the main page (or everywhere — your choice)
        var $container = $(this);
  var parallaxBg = document.getElementById("orbitalis-parallax-bg");
        var $buttons = $container.find('.hj-chaos-tab-button');
  if (!parallaxBg) return; // no parallax container on this page


        $buttons.on('click', function () {
  var nebulaEl = document.getElementById("parallax-nebula");
            var $btn = $(this);
  var starsSmallEl = document.getElementById("parallax-stars-small");
            var key = $btn.data('chaos');
  var starsBigEl = document.getElementById("parallax-stars-big");


            // Переключаем активную кнопку
  // Disable parallax on diff/comparison and old revision pages
            $buttons.removeClass('active');
  var params = new URLSearchParams(window.location.search);
            $btn.addClass('active');
  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 ----------------------------
            var $blocks = $container.nextAll('.hj-chaos-block');
  // We do it from JS because MediaWiki strips complex inline styles
            $blocks.removeClass('active');
 
            $blocks.filter('.hj-chaos-' + key).addClass('active');
  // 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);
  });
})();

Текущая версия от 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);
  });
})();