Jump to content

MediaWiki:Common.js: Difference between revisions

From Utopia Game
No edit summary
No edit summary
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */
/* Any JavaScript here will be loaded for all users on every page load. */


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


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


    // Tab click handler
  $('.wiki-tab-button').on('click', function () {
    $('.wiki-tab-button').on('click', function() {
    var $button = $(this);
        var $button = $(this);
    var tabId = $button.data('tab');
        var tabId = $button.data('tab');
    var $container = $button.closest('.wiki-tabs-container');
        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-button').removeClass('active');
    $container.find('.wiki-tab-pane').removeClass('active').hide();
        $container.find('.wiki-tab-pane').removeClass('active').hide();


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


// =========================================================
// Sticky header: centered TIME widgets container (SAFE)
// - Centers ONLY the time widgets
// - Leaves your Discord link exactly where it is
// - Does NOT use absolute positioning
// =========================================================
function getStickyTimeContainer() {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return null;


    let mid = document.getElementById('sticky-time-widgets');
/* =========================================================
    if (mid) return mid;
  Sticky Header Widgets (Discord + Clock | Game Date | Countdown)
  CLEAN + FIXED:
  - No CSS inside JS (your old paste had CSS at the bottom of JS)
  - No   spacing hacks (spacing should be CSS)
  - One shared container for the 3 widgets
  ========================================================= */
 
(function () {
  const DISCORD_URL = 'https://discord.gg/t2Rp2dRvze';


    mid = document.createElement('span');
  // Countdown target: Sat, 18 Apr 2026 00:00 UTC
    mid.id = 'sticky-time-widgets';
  const COUNTDOWN_TARGET_UTC = Date.parse('2026-04-18T00:00:00Z');
    mid.className = 'sticky-time-widgets';


    // Put container at the start; CSS will center it
  // Clock timezone (same for everyone)
    icons.prepend(mid);
  const CLOCK_TIMEZONE = 'Etc/UTC'; // change if desired


    return mid;
  // Game time anchor (real UTC -> game time)
}
  // At 2026-02-19 14:00:00 UTC, game time was Jan 21 YR2
  const GAME_ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
  const GAME_ANCHOR_MONTH_INDEX = 0; // Jan
  const GAME_ANCHOR_DAY = 21;        // 1..24
  const GAME_ANCHOR_YEAR = 2;        // YR2
  const GAME_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];


// =========================================================
  const pad2 = (n) => String(n).padStart(2, '0');
// Add Custom Sticky Header Link (Discord)
// =========================================================
mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;


  function ensureDiscordLink(icons) {
     if (document.getElementById('custom-sticky-link')) return;
     if (document.getElementById('custom-sticky-link')) return;


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


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


// =========================================================
  // Container that will hold ONLY the three widgets (not Discord, not default icons)
// Sticky Header Clock (timezone-stable, UTC)
  function getStickyTimeContainer() {
// =========================================================
mw.hook('wikipage.content').add(function () {
     const icons = document.querySelector('.vector-sticky-header-icons');
     const icons = document.querySelector('.vector-sticky-header-icons');
     if (!icons) return;
     if (!icons) return null;
 
    let mid = document.getElementById('sticky-time-widgets');
    if (mid) return mid;
 
    // Make sure Discord is created first so we can insert after it
    ensureDiscordLink(icons);
 
    mid = document.createElement('span');
    mid.id = 'sticky-time-widgets';
    mid.className = 'sticky-time-widgets';
 
    const discord = document.getElementById('custom-sticky-link');
    if (discord && discord.parentNode === icons) {
      discord.insertAdjacentElement('afterend', mid);
    } else {
      icons.prepend(mid);
    }
 
    return mid;
  }


  function ensureClock(mid) {
     if (document.getElementById('sticky-clock')) return;
     if (document.getElementById('sticky-clock')) return;
    const CLOCK_TIMEZONE = 'Etc/UTC';


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


     const label = document.createElement('span');
     const label = document.createElement('span');
     label.className = 'sticky-clock__label';
     label.className = 'header-widget__label';
     label.textContent = 'UTC:';
     label.textContent = (CLOCK_TIMEZONE === 'Etc/UTC') ? 'UTC:' : 'Time:';


     const value = document.createElement('span');
     const value = document.createElement('span');
     value.className = 'sticky-clock__value';
     value.className = 'header-widget__value';
    value.dataset.role = 'clock';


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


     const mid = getStickyTimeContainer();
  function ensureGame(mid) {
     if (mid) mid.appendChild(wrap);
    if (document.getElementById('sticky-game-time')) return;
 
     const wrap = document.createElement('span');
     wrap.id = 'sticky-game-time';
    wrap.className = 'sticky-game-time header-widget';


     const fmt = new Intl.DateTimeFormat('en-GB', {
     const value = document.createElement('span');
        timeZone: CLOCK_TIMEZONE,
    value.className = 'header-widget__value';
        hour: '2-digit',
    value.dataset.role = 'game';
        minute: '2-digit',
        second: '2-digit',
        hour12: false
    });


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


    updateClock();
  function ensureCountdown(mid) {
     setInterval(updateClock, 1000);
     if (document.getElementById('sticky-countdown')) return;
});


// Floating particles
    const wrap = document.createElement('span');
(function () {
    wrap.id = 'sticky-countdown';
  const canvas = document.createElement('canvas');
    wrap.className = 'sticky-countdown header-widget';
  canvas.id = 'particle-canvas';
  document.body.prepend(canvas);


  const ctx = canvas.getContext('2d');
    const label = document.createElement('span');
  const particles = [];
    label.className = 'header-widget__label';
  const COUNT = 80;
    label.textContent = 'Age ends in:';


  const COLORS = [
    const value = document.createElement('span');
    'rgba(0, 53, 102,  0.9)',  // #003566
     value.className = 'header-widget__value';
    'rgba(0, 29,  61,  0.8)',  // #001d3d
     value.dataset.role = 'countdown';
     'rgba(0,  8,  20,  0.7)',  // #000814
     'rgba(255, 255, 255, 0.15)' // subtle white glint
  ];


  function resize() {
    wrap.appendChild(label);
     canvas.width  = window.innerWidth;
     wrap.appendChild(value);
     canvas.height = window.innerHeight;
     mid.appendChild(wrap);
   }
   }


   function randomBetween(a, b) {
   function formatRemaining(ms) {
     return a + Math.random() * (b - a);
     if (ms <= 0) return '00:00:00';
  }


  function createParticle() {
    const totalSeconds = Math.floor(ms / 1000);
     return {
     const days = Math.floor(totalSeconds / 86400);
      x:      randomBetween(0, canvas.width),
    const hours = Math.floor((totalSeconds % 86400) / 3600);
      y:      randomBetween(canvas.height * 0.2, canvas.height),
    const minutes = Math.floor((totalSeconds % 3600) / 60);
      radius:  randomBetween(1.5, 5),
     const seconds = totalSeconds % 60;
      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++) {
    if (days > 0) return `${days}d ${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
    const p = createParticle();
     return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
     p.y = randomBetween(0, canvas.height);
    p.opacity = randomBetween(0, 1);
    particles.push(p);
   }
   }


   function draw() {
   function computeGameString(nowMs) {
     ctx.clearRect(0, 0, canvas.width, canvas.height);
     const hoursPassed = Math.floor((nowMs - GAME_ANCHOR_REAL_UTC) / 3600000);
 
    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;
    // Day increments every hour
      p.y    -= p.speedY;
    const dayIndex = (GAME_ANCHOR_DAY - 1) + hoursPassed;
      p.x    += p.speedX;
    const dayNumber = ((dayIndex % 24) + 24) % 24 + 1;


      if (p.y < -10 || p.life > 1.2) {
    // Month increments every 24 hours
        particles[i] = createParticle();
    const daysPassed = Math.floor(dayIndex / 24);
        return;
      }


      ctx.beginPath();
    // Month cycles (Jan..Jul)
      ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
    const totalMonthIndex = GAME_ANCHOR_MONTH_INDEX + daysPassed;
      ctx.globalAlpha = p.opacity;
    const monthIndex = ((totalMonthIndex % 7) + 7) % 7;
      ctx.fillStyle  = p.color;
      ctx.fill();


      ctx.shadowBlur  = 8;
    // Year increments every 7 real days
      ctx.shadowColor = p.color;
    const yearsPassed = Math.floor(totalMonthIndex / 7);
      ctx.fill();
    const year = GAME_ANCHOR_YEAR + yearsPassed;
      ctx.shadowBlur  = 0;
    });


     ctx.globalAlpha = 1;
     return `Current Game Date: ${GAME_MONTHS[monthIndex]} ${dayNumber} YR${year}`;
    requestAnimationFrame(draw);
   }
   }


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


// =========================================================
  function updateAll() {
// Sticky Header Game Time (GLOBAL FIXED CLOCK)
     const now = Date.now();
// 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 clockVal = document.querySelector('#sticky-time-widgets [data-role="clock"]');
    if (clockVal) clockVal.textContent = clockFmt.format(new Date(now));


     const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul'];
     const gameVal = document.querySelector('#sticky-time-widgets [data-role="game"]');
    if (gameVal) gameVal.textContent = computeGameString(now);


     const ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
     const countdownVal = document.querySelector('#sticky-time-widgets [data-role="countdown"]');
    const ANCHOR_MONTH_INDEX = 0; // Jan
     if (countdownVal) countdownVal.textContent = formatRemaining(COUNTDOWN_TARGET_UTC - now);
    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 mid = getStickyTimeContainer();
     if (mid) mid.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 `&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Current Game Date:&nbsp;${months[monthIndex]} ${dayNumber} YR${year}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`;
    }
 
    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);
});


 
  mw.hook('wikipage.content').add(function () {
// =========================================================
// 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');
     const icons = document.querySelector('.vector-sticky-header-icons');
     if (!icons) return;
     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 mid = getStickyTimeContainer();
     const mid = getStickyTimeContainer();
     if (mid) mid.appendChild(wrap);
     if (!mid) return;


     const pad = (n) => String(n).padStart(2, '0');
     ensureClock(mid);
    ensureGame(mid);
    ensureCountdown(mid);


     const formatRemaining = (ms) => {
     // Start update loop once
        if (ms <= 0) return '00:00:00';
    if (!window.__stickyTimeWidgetsStarted) {
        const totalSeconds = Math.floor(ms / 1000);
      window.__stickyTimeWidgetsStarted = true;
      updateAll();
      setInterval(updateAll, 1000);
    } else {
      // If navigating, ensure values refresh immediately
      updateAll();
    }
  });


        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 });
});

Revision as of 10:53, 19 February 2026

/* Any JavaScript here will be loaded for all users on every page load. */

/* =========================================================
   Tabs (unchanged)
   ========================================================= */
$(document).ready(function () {
  $('.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();
  });

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

    $container.find('.wiki-tab-button').removeClass('active');
    $container.find('.wiki-tab-pane').removeClass('active').hide();

    $button.addClass('active');
    $container.find('#' + tabId).addClass('active').show();
  });
});


/* =========================================================
   Sticky Header Widgets (Discord + Clock | Game Date | Countdown)
   CLEAN + FIXED:
   - No CSS inside JS (your old paste had CSS at the bottom of JS)
   - No &nbsp; spacing hacks (spacing should be CSS)
   - One shared container for the 3 widgets
   ========================================================= */

(function () {
  const DISCORD_URL = 'https://discord.gg/t2Rp2dRvze';

  // Countdown target: Sat, 18 Apr 2026 00:00 UTC
  const COUNTDOWN_TARGET_UTC = Date.parse('2026-04-18T00:00:00Z');

  // Clock timezone (same for everyone)
  const CLOCK_TIMEZONE = 'Etc/UTC'; // change if desired

  // Game time anchor (real UTC -> game time)
  // At 2026-02-19 14:00:00 UTC, game time was Jan 21 YR2
  const GAME_ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
  const GAME_ANCHOR_MONTH_INDEX = 0; // Jan
  const GAME_ANCHOR_DAY = 21;        // 1..24
  const GAME_ANCHOR_YEAR = 2;        // YR2
  const GAME_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];

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

  function ensureDiscordLink(icons) {
    if (document.getElementById('custom-sticky-link')) return;

    const link = document.createElement('a');
    link.id = 'custom-sticky-link';
    link.href = DISCORD_URL;
    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';

    icons.prepend(link);
  }

  // Container that will hold ONLY the three widgets (not Discord, not default icons)
  function getStickyTimeContainer() {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return null;

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

    // Make sure Discord is created first so we can insert after it
    ensureDiscordLink(icons);

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

    const discord = document.getElementById('custom-sticky-link');
    if (discord && discord.parentNode === icons) {
      discord.insertAdjacentElement('afterend', mid);
    } else {
      icons.prepend(mid);
    }

    return mid;
  }

  function ensureClock(mid) {
    if (document.getElementById('sticky-clock')) return;

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

    const label = document.createElement('span');
    label.className = 'header-widget__label';
    label.textContent = (CLOCK_TIMEZONE === 'Etc/UTC') ? 'UTC:' : 'Time:';

    const value = document.createElement('span');
    value.className = 'header-widget__value';
    value.dataset.role = 'clock';

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

  function ensureGame(mid) {
    if (document.getElementById('sticky-game-time')) return;

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

    const value = document.createElement('span');
    value.className = 'header-widget__value';
    value.dataset.role = 'game';

    wrap.appendChild(value);
    mid.appendChild(wrap);
  }

  function ensureCountdown(mid) {
    if (document.getElementById('sticky-countdown')) return;

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

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

    const value = document.createElement('span');
    value.className = 'header-widget__value';
    value.dataset.role = 'countdown';

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

  function 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 ${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
    return `${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}`;
  }

  function computeGameString(nowMs) {
    const hoursPassed = Math.floor((nowMs - GAME_ANCHOR_REAL_UTC) / 3600000);

    // Day increments every hour
    const dayIndex = (GAME_ANCHOR_DAY - 1) + hoursPassed;
    const dayNumber = ((dayIndex % 24) + 24) % 24 + 1;

    // Month increments every 24 hours
    const daysPassed = Math.floor(dayIndex / 24);

    // Month cycles (Jan..Jul)
    const totalMonthIndex = GAME_ANCHOR_MONTH_INDEX + daysPassed;
    const monthIndex = ((totalMonthIndex % 7) + 7) % 7;

    // Year increments every 7 real days
    const yearsPassed = Math.floor(totalMonthIndex / 7);
    const year = GAME_ANCHOR_YEAR + yearsPassed;

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

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

  function updateAll() {
    const now = Date.now();

    const clockVal = document.querySelector('#sticky-time-widgets [data-role="clock"]');
    if (clockVal) clockVal.textContent = clockFmt.format(new Date(now));

    const gameVal = document.querySelector('#sticky-time-widgets [data-role="game"]');
    if (gameVal) gameVal.textContent = computeGameString(now);

    const countdownVal = document.querySelector('#sticky-time-widgets [data-role="countdown"]');
    if (countdownVal) countdownVal.textContent = formatRemaining(COUNTDOWN_TARGET_UTC - now);
  }

  mw.hook('wikipage.content').add(function () {
    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    const mid = getStickyTimeContainer();
    if (!mid) return;

    ensureClock(mid);
    ensureGame(mid);
    ensureCountdown(mid);

    // Start update loop once
    if (!window.__stickyTimeWidgetsStarted) {
      window.__stickyTimeWidgetsStarted = true;
      updateAll();
      setInterval(updateAll, 1000);
    } else {
      // If navigating, ensure values refresh immediately
      updateAll();
    }
  });

})();