Jump to content

MediaWiki:Common.js

From Utopia Game
Revision as of 10:30, 19 February 2026 by Sonja says (talk | contribs)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */

// Tab functionality
$(document).ready(function() {
    // Initialize tabs - show first tab by default
    $('.wiki-tabs-container').each(function() {
        var $container = $(this);
        var $firstButton = $container.find('.wiki-tab-button').first();
        var firstTabId = $firstButton.data('tab');

        $firstButton.addClass('active');
        $container.find('#' + firstTabId).addClass('active').show();
    });

    // Tab click handler
    $('.wiki-tab-button').on('click', function() {
        var $button = $(this);
        var tabId = $button.data('tab');
        var $container = $button.closest('.wiki-tabs-container');

        // Remove active class from all buttons and panes in this container
        $container.find('.wiki-tab-button').removeClass('active');
        $container.find('.wiki-tab-pane').removeClass('active').hide();

        // Add active class to clicked button and corresponding pane
        $button.addClass('active');
        $container.find('#' + tabId).addClass('active').show();
    });
});

// =========================================================
// Sticky header: 3-zone layout container (Clock | Game Date | Countdown)
// (Does NOT move your Discord link or default icons)
// =========================================================
function getStickyTimeZones() {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return null;

    let bar = document.getElementById('sticky-time-bar');
    if (bar) return bar;

    bar = document.createElement('span');
    bar.id = 'sticky-time-bar';
    bar.className = 'sticky-time-bar';

    const left = document.createElement('span');
    left.id = 'sticky-time-left';
    left.className = 'sticky-time-left';

    const center = document.createElement('span');
    center.id = 'sticky-time-center';
    center.className = 'sticky-time-center';

    const right = document.createElement('span');
    right.id = 'sticky-time-right';
    right.className = 'sticky-time-right';

    bar.appendChild(left);
    bar.appendChild(center);
    bar.appendChild(right);

    // Insert AFTER your Discord link (so Discord stays far-left)
    const discord = document.getElementById('custom-sticky-link');
    if (discord && discord.parentNode === icons) {
        discord.insertAdjacentElement('afterend', bar);
    } else {
        icons.prepend(bar);
    }

    return bar;
}

// =========================================================
// Add Custom Sticky Header Link (Discord)
// =========================================================
mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    if (document.getElementById('custom-sticky-link')) return;

    const link = document.createElement('a');
    link.id = 'custom-sticky-link';
    link.href = 'https://discord.gg/t2Rp2dRvze';
    link.target = '_blank';
    link.className = 'cdx-button cdx-button--fake-button cdx-button--fake-button--enabled cdx-button--weight-quiet';
    link.textContent = 'Join Us on Discord!';
    link.style.marginLeft = '10px';
    link.style.fontWeight = 'bold';

    // Keep it exactly where you had it
    icons.prepend(link);
});

// =========================================================
// Sticky Header Clock (timezone-stable, UTC)
// =========================================================
mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    if (document.getElementById('sticky-clock')) return;

    const CLOCK_TIMEZONE = 'Etc/UTC';

    const wrap = document.createElement('span');
    wrap.id = 'sticky-clock';
    wrap.className = 'sticky-clock';

    const label = document.createElement('span');
    label.className = 'sticky-clock__label';
    label.textContent = 'UTC:';

    const value = document.createElement('span');
    value.className = 'sticky-clock__value';

    wrap.appendChild(label);
    wrap.appendChild(value);

    const zones = getStickyTimeZones();
    if (zones) document.getElementById('sticky-time-left').appendChild(wrap);


    const fmt = new Intl.DateTimeFormat('en-GB', {
        timeZone: CLOCK_TIMEZONE,
        hour: '2-digit',
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    });

    function updateClock() {
        value.textContent = fmt.format(new Date());
    }

    updateClock();
    setInterval(updateClock, 1000);
});

// Floating particles
(function () {
  const canvas = document.createElement('canvas');
  canvas.id = 'particle-canvas';
  document.body.prepend(canvas);

  const ctx = canvas.getContext('2d');
  const particles = [];
  const COUNT = 80;

  const COLORS = [
    'rgba(0, 53, 102,  0.9)',   // #003566
    'rgba(0, 29,  61,  0.8)',   // #001d3d
    'rgba(0,  8,  20,  0.7)',   // #000814
    'rgba(255, 255, 255, 0.15)' // subtle white glint
  ];

  function resize() {
    canvas.width  = window.innerWidth;
    canvas.height = window.innerHeight;
  }

  function randomBetween(a, b) {
    return a + Math.random() * (b - a);
  }

  function createParticle() {
    return {
      x:       randomBetween(0, canvas.width),
      y:       randomBetween(canvas.height * 0.2, canvas.height),
      radius:  randomBetween(1.5, 5),
      color:   COLORS[Math.floor(Math.random() * COLORS.length)],
      speedY:  randomBetween(0.2, 0.7),
      speedX:  randomBetween(-0.2, 0.2),
      opacity: 0,
      fadeIn:  randomBetween(0.003, 0.008),
      life:    randomBetween(0.4, 1)
    };
  }

  for (let i = 0; i < COUNT; i++) {
    const p = createParticle();
    p.y = randomBetween(0, canvas.height);
    p.opacity = randomBetween(0, 1);
    particles.push(p);
  }

  function draw() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    particles.forEach((p, i) => {
      if (p.life < 0.8) {
        p.opacity = Math.min(1, p.opacity + p.fadeIn);
      } else {
        p.opacity = Math.max(0, p.opacity - p.fadeIn * 0.5);
      }

      p.life += 0.001;
      p.y    -= p.speedY;
      p.x    += p.speedX;

      if (p.y < -10 || p.life > 1.2) {
        particles[i] = createParticle();
        return;
      }

      ctx.beginPath();
      ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
      ctx.globalAlpha = p.opacity;
      ctx.fillStyle   = p.color;
      ctx.fill();

      ctx.shadowBlur  = 8;
      ctx.shadowColor = p.color;
      ctx.fill();
      ctx.shadowBlur  = 0;
    });

    ctx.globalAlpha = 1;
    requestAnimationFrame(draw);
  }

  resize();
  draw();
  window.addEventListener('resize', resize);
})();

// =========================================================
// Sticky Header Countdown Timer (to Apr 18, 2026 00:00 UTC)
// =========================================================
mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    if (document.getElementById('sticky-countdown')) return;

    const target = new Date('2026-04-18T00:00:00Z');

    const wrap = document.createElement('span');
    wrap.id = 'sticky-countdown';
    wrap.className = 'sticky-countdown';

    const label = document.createElement('span');
    label.className = 'sticky-countdown__label';
    label.textContent = 'Age ends in:';

    const value = document.createElement('span');
    value.className = 'sticky-countdown__value';
    value.textContent = '--:--:--';

    wrap.appendChild(label);
    wrap.appendChild(value);

    const zones = getStickyTimeZones();
    if (zones) document.getElementById('sticky-time-right').appendChild(wrap);


    const pad = (n) => String(n).padStart(2, '0');

    const formatRemaining = (ms) => {
        if (ms <= 0) return '00:00:00';
        const totalSeconds = Math.floor(ms / 1000);

        const days = Math.floor(totalSeconds / 86400);
        const hours = Math.floor((totalSeconds % 86400) / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;

        if (days > 0) return `${days}d ${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
        return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
    };

    const update = () => {
        const diff = target.getTime() - Date.now();
        value.textContent = formatRemaining(diff);
        wrap.classList.toggle('is-expired', diff <= 0);
    };

    update();
    const timerId = window.setInterval(update, 1000);

    // Cleanup if removed
    const observer = new MutationObserver(() => {
        if (!document.getElementById('sticky-countdown')) {
            clearInterval(timerId);
            observer.disconnect();
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });
});

// =========================================================
// Sticky Header Game Time (GLOBAL FIXED CLOCK)
// Rules:
// - 1 real hour = 1 in-game day (1..24)
// - 1 real day = 1 in-game month (Jan..Jul cycle)
// - 1 real week (7 real days) = 1 in-game year
// Anchor given: at 2026-02-19 14:00:00 UTC => Jan 21 YR2
// =========================================================
mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    if (document.getElementById('sticky-game-time')) return;

    const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul'];

    const ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
    const ANCHOR_MONTH_INDEX = 0; // Jan
    const ANCHOR_DAY = 21;        // 1..24
    const ANCHOR_YEAR = 2;        // YR2

    const wrap = document.createElement('span');
    wrap.id = 'sticky-game-time';
    wrap.className = 'sticky-game-time';

    const value = document.createElement('span');
    value.className = 'sticky-game-time__value';
    wrap.appendChild(value);

    const zones = getStickyTimeZones();
    if (zones) document.getElementById('sticky-time-center').appendChild(wrap);


    function computeGameString() {
        const hoursPassed = Math.floor((Date.now() - ANCHOR_REAL_UTC) / 3600000);

        const dayIndex = (ANCHOR_DAY - 1) + hoursPassed;
        const dayNumber = ((dayIndex % 24) + 24) % 24 + 1;

        const daysPassed = Math.floor(dayIndex / 24);

        const totalMonthIndex = ANCHOR_MONTH_INDEX + daysPassed;
        const monthIndex = ((totalMonthIndex % 7) + 7) % 7;

        const yearsPassed = Math.floor(totalMonthIndex / 7);
        const year = ANCHOR_YEAR + yearsPassed;

        return `Current Game Date:&nbsp;${months[monthIndex]} ${dayNumber} YR${year}`;
    }

    function update() {
        value.innerHTML = computeGameString();
    }

    update();

    // Update on next hour boundary, then hourly
    const d = new Date();
    const msUntilNextHour =
        3600000 - (d.getMinutes() * 60000 + d.getSeconds() * 1000 + d.getMilliseconds());

    setTimeout(function () {
        update();
        setInterval(update, 3600000);
    }, msUntilNextHour);
});