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

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

Материал из Horny Jail WIKI
Нет описания правки
Метка: отменено
Нет описания правки
 
(не показано 18 промежуточных версий этого же участника)
Строка 1: Строка 1:
/**
/* MediaWiki:Common.js — Orbitalis Wiki
  * Horny Jail Wiki - Enhanced JavaScript
* This code runs on every wiki page.
  * Handles tabs, animations, and interactive elements
* 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;
  }


mw.hook('wikipage.content').add(function ($root) {
  // ---- Apply styles to the parallax elements ----------------------------
    
  // We do it from JS because MediaWiki strips complex inline styles
   // ================== CHAOS TABS SYSTEM ==================
 
  $root.find('.hj-chaos-container').each(function () {
  // Background container
     var $container = $(this);
  parallaxBg.style.cssText =
    var $buttons = $container.find('.hj-chaos-tab-button');
    "position:fixed;top:0;left:0;width:100%;height:100%;z-index:-10;pointer-events:none;overflow:hidden;background:#060608;";
    var $blocks = $container.find('.hj-chaos-block');
 
  // 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;";
  }


    if (!$buttons.length || !$blocks.length) return;
  // 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%);";
  }


    var CHAOS_CLASSES = 'chaos-overview chaos-calm chaos-medium chaos-high';
  // ---- Override MediaWiki default backgrounds ----------------------------
  document.body.style.background = "transparent";


    function activate(key, $btn) {
  var ids = ["mw-page-base", "mw-head-base"];
      // Active button
  for (var i = 0; i < ids.length; i++) {
      $buttons.removeClass('active');
    var el = document.getElementById(ids[i]);
      $btn.addClass('active');
    if (el) el.style.background = "none";
  }


      // Active block with fade animation
  var content = document.getElementById("content");
      $blocks.removeClass('active').hide();
  if (content) {
      var $targetBlock = $blocks.filter('[data-chaos="' + key + '"]');
    content.style.background = "none";
      $targetBlock.addClass('active').fadeIn(200);
    content.style.border = "none";
  }


      // Container class for styling
  var bodyContent = document.getElementById("bodyContent");
      $container.removeClass(CHAOS_CLASSES).addClass('chaos-' + key);
  if (bodyContent) {
    }
    bodyContent.style.position = "relative";
  }


    // Initialize
  // ---- Mouse-tracking parallax animation --------------------------------
    var $activeBtn = $buttons.filter('.active').first();
  var targetX = 0,
     if ($activeBtn.length) {
     targetY = 0;
      activate($activeBtn.data('chaos'), $activeBtn);
  var currentX = 0,
    } else {
     currentY = 0;
      var $first = $buttons.first();
      activate($first.data('chaos'), $first);
     }


    // Click handlers
  document.addEventListener("mousemove", function (e) {
    $buttons.off('click.hjChaos').on('click.hjChaos', function () {
    var w = window.innerWidth || document.documentElement.clientWidth;
      var $btn = $(this);
    var h = window.innerHeight || document.documentElement.clientHeight;
      activate($btn.data('chaos'), $btn);
    targetX = (e.clientX / w - 0.5) * 2;
     });
     targetY = (e.clientY / h - 0.5) * 2;
   });
   });


   // ================== CARD HOVER EFFECTS ==================
   function animate() {
  $root.find('.hj-nav-card').each(function () {
     currentX += (targetX - currentX) * 0.06;
     var $card = $(this);
     currentY += (targetY - currentY) * 0.06;
      
    $card.on('mouseenter', function (e) {
      // Add subtle glow effect on mouse position
      var rect = this.getBoundingClientRect();
      var x = e.clientX - rect.left;
      var y = e.clientY - rect.top;
     
      $card.css({
        '--mouse-x': x + 'px',
        '--mouse-y': y + 'px'
      });
    });
  });


  // ================== ANIMATE ON SCROLL ==================
    if (nebulaEl) {
  if ('IntersectionObserver' in window) {
      nebulaEl.style.left = currentX * 12 - 50 + "px";
    var observer = new IntersectionObserver(function (entries) {
       nebulaEl.style.top = currentY * 12 - 50 + "px";
       entries.forEach(function (entry) {
    }
        if (entry.isIntersecting) {
    if (starsSmallEl) {
          $(entry.target).addClass('hj-visible');
      starsSmallEl.style.left = currentX * 25 - 50 + "px";
          observer.unobserve(entry.target);
      starsSmallEl.style.top = currentY * 25 - 50 + "px";
        }
    }
      });
    if (starsBigEl) {
    }, {
       starsBigEl.style.left = currentX * 40 - 50 + "px";
       threshold: 0.1,
       starsBigEl.style.top = currentY * 40 - 50 + "px";
       rootMargin: '0px 0px -50px 0px'
     }
     });


     $root.find('.hj-animate').each(function () {
     requestAnimationFrame(animate);
      observer.observe(this);
    });
  } else {
    // Fallback for older browsers
    $root.find('.hj-animate').addClass('hj-visible');
   }
   }


   // ================== SMOOTH SCROLL FOR ANCHORS ==================
   animate();
   $root.find('a[href^="#"]').on('click', function (e) {
})();
     var target = $(this.getAttribute('href'));
 
    if (target.length) {
/* ---- Server online status + info ---- */
      e.preventDefault();
/* Queries /api/status.php on the same wiki host — which sends a UDP BYOND
       $('html, body').animate({
* topic to localhost:41060 and returns JSON { players, map_name, chaos_level, ... }.
         scrollTop: target.offset().top - 100
* No proxy needed: wiki and game server share the same machine.
      }, 500, 'swing');
*/
    }
(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";


  // ================== COUNTER ANIMATION ==================
        if (elMap && data.map_name) {
  $root.find('.hj-stat-value[data-count]').each(function () {
          elMap.textContent = data.map_name;
    var $this = $(this);
          elMap.style.color = "rgba(255,255,255,0.55)";
    var countTo = parseInt($this.data('count'), 10);
   
    if (!countTo) return;
   
    var observer = new IntersectionObserver(function (entries) {
      entries.forEach(function (entry) {
        if (entry.isIntersecting) {
          $({ count: 0 }).animate({ count: countTo }, {
            duration: 1500,
            easing: 'swing',
            step: function () {
              $this.text(Math.floor(this.count));
            },
            complete: function () {
              $this.text(countTo);
            }
          });
          observer.unobserve(entry.target);
         }
         }
        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 = "—";
       });
       });
    }, { threshold: 0.5 });
  }


    observer.observe(this);
  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";


   // ================== TOOLTIP SYSTEM ==================
   function run() {
  $root.find('[data-hj-tooltip]').each(function () {
     var orb = document.getElementById("orb-content");
     var $el = $(this);
     if (!orb) return;
    var tooltipText = $el.data('hj-tooltip');
   
    var $tooltip = $('<div class="hj-tooltip">' + tooltipText + '</div>');
    $tooltip.css({
      position: 'absolute',
      background: 'rgba(0, 0, 0, 0.9)',
      color: '#fff',
      padding: '8px 12px',
      borderRadius: '6px',
      fontSize: '0.85em',
      zIndex: 9999,
      pointerEvents: 'none',
      opacity: 0,
      transition: 'opacity 0.2s ease',
      maxWidth: '250px',
      whiteSpace: 'normal'
    });
   
    $el.css('position', 'relative');
   
    $el.on('mouseenter', function (e) {
      $('body').append($tooltip);
      var rect = this.getBoundingClientRect();
      $tooltip.css({
        top: rect.top + window.scrollY - $tooltip.outerHeight() - 8,
        left: rect.left + (rect.width / 2) - ($tooltip.outerWidth() / 2),
        opacity: 1
      });
    });
      
    $el.on('mouseleave', function () {
      $tooltip.remove();
    });
  });


  // ================== COLLAPSIBLE SECTIONS ==================
    /* Nuclear strip: clear Citizen-applied backgrounds on <a> tags only */
  $root.find('.hj-collapsible-header').on('click', function () {
     var links = orb.querySelectorAll("a");
     var $header = $(this);
     for (var i = 0; i < links.length; i++) {
     var $content = $header.next('.hj-collapsible-content');
      var a = links[i];
    var $icon = $header.find('.hj-collapsible-icon');
      a.style.setProperty("background", "none", "important");
   
      a.style.setProperty("background-color", "transparent", "important");
    $content.slideToggle(200);
      a.style.setProperty("background-image", "none", "important");
    $header.toggleClass('hj-collapsed');
      a.style.setProperty("box-shadow", "none", "important");
   
      a.style.setProperty("border", "none", "important");
    if ($header.hasClass('hj-collapsed')) {
       a.style.setProperty("padding", "0", "important");
       $icon.text('▶');
       a.style.setProperty("text-decoration", "none", "important");
    } else {
       $icon.text('▼');
     }
     }
  });


  // ================== COPY CODE BUTTON ==================
    /* Connect button — byond:// link */
  $root.find('pre').each(function () {
     var addr = document.getElementById("orb-addr");
     var $pre = $(this);
     if (addr && !addr._orbClick) {
    var $btn = $('<button class="hj-copy-btn" title="Скопировать">📋</button>');
       addr._orbClick = true;
   
       addr.addEventListener("click", function () {
    $btn.css({
        window.location.href = "byond://45.141.208.222:41060";
      position: 'absolute',
      top: '8px',
      right: '8px',
      background: 'rgba(255,255,255,0.1)',
      border: 'none',
      borderRadius: '4px',
      padding: '4px 8px',
      cursor: 'pointer',
      opacity: 0,
      transition: 'opacity 0.2s'
    });
   
    $pre.css('position', 'relative');
     $pre.append($btn);
   
    $pre.on('mouseenter', function () {
       $btn.css('opacity', 1);
    }).on('mouseleave', function () {
       $btn.css('opacity', 0);
    });
   
    $btn.on('click', function () {
      var text = $pre.text().replace('📋', '').trim();
      navigator.clipboard.writeText(text).then(function () {
        $btn.text('✓');
        setTimeout(function () {
          $btn.text('📋');
        }, 1500);
       });
       });
     });
     }
  });


  // ================== PARALLAX HERO ==================
    /* Make entire card area clickable */
  var $hero = $root.find('.hj-hero');
    var cardIds = [
  if ($hero.length) {
      "orb-card-1",
    $(window).on('scroll.hjParallax', function () {
      "orb-card-2",
      var scrolled = $(window).scrollTop();
      "orb-card-3",
      var heroHeight = $hero.outerHeight();
      "orb-card-4",
     
      "orb-card-5",
      if (scrolled < heroHeight) {
      "orb-card-6",
        var parallaxValue = scrolled * 0.3;
      "orb-card-7",
        $hero.find('.hj-hero-logo').css('transform', 'translateY(' + (parallaxValue * 0.5) + 'px)');
      "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);
  });
})();


  // ================== TYPING EFFECT ==================
// Переключение вкладок и классов "chaos-*".
   $root.find('.hj-typing').each(function () {
mw.hook("wikipage.content").add(function ($root) {
     var $el = $(this);
   $root.find(".hj-chaos-container").each(function () {
     var text = $el.text();
     var $container = $(this);
     var speed = parseInt($el.data('typing-speed'), 10) || 50;
     var $buttons = $container.find(".hj-chaos-tab-button");
      
     var $blocks = $container.find(".hj-chaos-block");
     $el.text('');
 
    $el.css('visibility', 'visible');
    if (!$buttons.length || !$blocks.length) return;
      
 
     var i = 0;
     var CHAOS_CLASSES = "chaos-overview chaos-calm chaos-medium chaos-high";
     function type() {
 
       if (i < text.length) {
     function activate(key, $btn) {
        $el.text($el.text() + text.charAt(i));
      // активная кнопка
        i++;
      $buttons.removeClass("active");
        setTimeout(type, speed);
      $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);
     }
     }
   
 
     // Start typing when visible
     // Клики
     var observer = new IntersectionObserver(function (entries) {
     $buttons.off("click.hjChaos").on("click.hjChaos", function () {
       if (entries[0].isIntersecting) {
       var $btn = $(this);
        type();
      activate($btn.data("chaos"), $btn);
        observer.unobserve($el[0]);
      }
     });
     });
    observer.observe($el[0]);
   });
   });
});
});


// ================== CSS ANIMATIONS (injected) ==================
/* ---- 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 () {
(function () {
   var style = document.createElement('style');
   "use strict";
  style.textContent = `
 
     .hj-animate {
  function initOrbToc() {
       opacity: 0;
    // Already built?
       transform: translateY(20px);
    if (document.getElementById("orb-toc-sidebar")) return;
       transition: opacity 0.6s ease, transform 0.6s ease;
 
    // 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 });
     }
     }
      
 
     .hj-animate.hj-visible {
     if (items.length < 2) return;
       opacity: 1;
 
       transform: translateY(0);
     // --- 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);
     .hj-animate-delay-1 { transition-delay: 0.1s; }
 
     .hj-animate-delay-2 { transition-delay: 0.2s; }
     // Append to body - outside any Citizen container
     .hj-animate-delay-3 { transition-delay: 0.3s; }
     document.body.appendChild(sidebar);
     .hj-animate-delay-4 { transition-delay: 0.4s; }
 
      
     // --- Scroll spy ---
     .hj-collapsible-header {
     var allLi = ul.querySelectorAll("li");
       cursor: pointer;
     var headingEls = [];
      user-select: none;
     for (var k = 0; k < items.length; k++) {
       headingEls.push(items[k].el);
     }
     }
      
 
     .hj-collapsible-header:hover {
     var ticking = false;
       opacity: 0.8;
     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");
          }
        }
       });
     }
     }
   
 
     .hj-typing {
     window.addEventListener("scroll", onScroll, { passive: true });
      visibility: hidden;
     onScroll();
     }
  }
   `;
 
   document.head.appendChild(style);
   // 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);
  });
})();