Jump to content

MediaWiki:Common.js: Difference between revisions

From Utopia Game
No edit summary
No edit summary
Line 8: Line 8:
         var $firstButton = $container.find('.wiki-tab-button').first();
         var $firstButton = $container.find('.wiki-tab-button').first();
         var firstTabId = $firstButton.data('tab');
         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
     // Tab click handler
     $('.wiki-tab-button').on('click', function() {
     $('.wiki-tab-button').on('click', function() {
Line 18: Line 18:
         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
         // 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
         // Add active class to clicked button and corresponding pane
         $button.addClass('active');
         $button.addClass('active');
Line 29: Line 29:
});
});


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


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


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


     // Put it inside the sticky header (positioned via CSS)
     // Put container at the start; CSS will center it
     sticky.appendChild(center);
     icons.prepend(mid);


     return center;
     return mid;
}
}


 
// =========================================================
// ===============================
// Add Custom Sticky Header Link (Discord) - unchanged
// Add Custom Sticky Header Link
// =========================================================
// ===============================
mw.hook('wikipage.content').add(function () {
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;


    // Prevent duplicate button on navigation
     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'; // CHANGE THIS
     link.href = 'https://discord.gg/t2Rp2dRvze';
     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!'; // CHANGE TEXT
     link.textContent = 'Join Us on Discord!';
     link.style.marginLeft = '10px';
     link.style.marginLeft = '10px';
     link.style.fontWeight = 'bold';
     link.style.fontWeight = 'bold';


     icons.prepend(link); // use appendChild() if you want it at the end
    // Keep it exactly where you had it
     icons.prepend(link);
});
});


// ===============================
// =========================================================
// Sticky Header Countdown Timer (Vector 2022) - to Apr 18, 2026 00:00 UTC
// Sticky Header Countdown Timer (to Apr 18, 2026 00:00 UTC)
// ===============================
// =========================================================
mw.hook('wikipage.content').add(function () {
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;


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


    // Target: Sat, 18 Apr 2026 at 00:00 GMT+00:00 (UTC)
     const target = new Date('2026-04-18T00:00:00Z');
     const target = new Date('2026-04-18T00:00:00Z');


Line 101: Line 100:
     wrap.appendChild(value);
     wrap.appendChild(value);


    // Put it before the default icons (Talk/History/Edit)
     const mid = getStickyTimeContainer();
     const center = getStickyCenterContainer();
     if (mid) mid.appendChild(wrap);
     if (center) center.appendChild(wrap);
    else icons.prepend(wrap); // fallback
 


     const pad = (n) => String(n).padStart(2, '0');
     const pad = (n) => String(n).padStart(2, '0');
Line 123: Line 119:


     const update = () => {
     const update = () => {
        const now = new Date();
         const diff = target.getTime() - Date.now();
         const diff = target.getTime() - now.getTime();
         value.textContent = formatRemaining(diff);
         value.textContent = formatRemaining(diff);
         wrap.classList.toggle('is-expired', diff <= 0);
         wrap.classList.toggle('is-expired', diff <= 0);
Line 132: Line 127:
     const timerId = window.setInterval(update, 1000);
     const timerId = window.setInterval(update, 1000);


     // Cleanup if element removed (rare, but safe)
     // Cleanup if removed
     const observer = new MutationObserver(() => {
     const observer = new MutationObserver(() => {
         if (!document.getElementById('sticky-countdown')) {
         if (!document.getElementById('sticky-countdown')) {
Line 142: Line 137:
});
});


// ===============================
// =========================================================
 
 
 
// ===============================
// Sticky Header Game Time (GLOBAL FIXED CLOCK)
// Sticky Header Game Time (GLOBAL FIXED CLOCK)
// Rules:
// Rules:
Line 152: Line 143:
// - 1 real day = 1 in-game month (Jan..Jul cycle)
// - 1 real day = 1 in-game month (Jan..Jul cycle)
// - 1 real week (7 real days) = 1 in-game year
// - 1 real week (7 real days) = 1 in-game year
// Display: "Jan 21 YR2"
// Anchor given: at 2026-02-19 14:00:00 UTC => Jan 21 YR2
// ===============================
// =========================================================
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {
     const icons = document.querySelector('.vector-sticky-header-icons');
     const icons = document.querySelector('.vector-sticky-header-icons');
Line 162: Line 153:
     const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul'];
     const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul'];


    // -------------------------------
    // GLOBAL REAL-WORLD ANCHOR (UTC)
    // At this real time, the game time was:
    // Jan 21 YR2
    // -------------------------------
     const ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
     const ANCHOR_REAL_UTC = Date.parse('2026-02-19T14:00:00Z');
     const ANCHOR_MONTH_INDEX = 0; // Jan
     const ANCHOR_MONTH_INDEX = 0; // Jan
     const ANCHOR_DAY = 21;        // Day 21
     const ANCHOR_DAY = 21;        // 1..24
     const ANCHOR_YEAR = 2;        // YR2
     const ANCHOR_YEAR = 2;        // YR2


    // UI pill
     const wrap = document.createElement('span');
     const wrap = document.createElement('span');
     wrap.id = 'sticky-game-time';
     wrap.id = 'sticky-game-time';
Line 180: Line 164:
     const value = document.createElement('span');
     const value = document.createElement('span');
     value.className = 'sticky-game-time__value';
     value.className = 'sticky-game-time__value';
    wrap.appendChild(value);


    wrap.appendChild(value);
     const mid = getStickyTimeContainer();
     const center = getStickyCenterContainer();
     if (mid) mid.appendChild(wrap);
     if (center) center.appendChild(wrap);
    else icons.prepend(wrap); // fallback


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


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


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


        // Month is 7-day cycle (Jan..Jul)
         const totalMonthIndex = ANCHOR_MONTH_INDEX + daysPassed;
         const totalMonthIndex = ANCHOR_MONTH_INDEX + daysPassed;
         const monthIndex = ((totalMonthIndex % 7) + 7) % 7;
         const monthIndex = ((totalMonthIndex % 7) + 7) % 7;


        // Year increments every 7 days
         const yearsPassed = Math.floor(totalMonthIndex / 7);
         const yearsPassed = Math.floor(totalMonthIndex / 7);
         const year = ANCHOR_YEAR + yearsPassed;
         const year = ANCHOR_YEAR + yearsPassed;
Line 215: Line 192:
     update();
     update();


     // Update on the next real hour boundary, then every hour
     // Update on next hour boundary, then hourly
     const d = new Date();
     const d = new Date();
     const msUntilNextHour =
     const msUntilNextHour =
Line 226: Line 203:
});
});


 
// =========================================================
// ===============================
// Sticky Header Clock (timezone-stable, UTC)
// Sticky Header Clock (timezone-stable)
// =========================================================
// Shows the same time for everyone by forcing a timezone (UTC by default)
// ===============================
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {
     const icons = document.querySelector('.vector-sticky-header-icons');
     const icons = document.querySelector('.vector-sticky-header-icons');
Line 237: Line 212:
     if (document.getElementById('sticky-clock')) return;
     if (document.getElementById('sticky-clock')) return;


    // Choose the clock timezone:
     const CLOCK_TIMEZONE = 'Etc/UTC';
    // - "Etc/UTC" for UTC (same for all users)
    // - Or "America/New_York" (same for all users, displayed in NY time)
     const CLOCK_TIMEZONE = 'Etc/UTC'; // <-- change if you want a specific zone


     const wrap = document.createElement('span');
     const wrap = document.createElement('span');
Line 248: Line 220:
     const label = document.createElement('span');
     const label = document.createElement('span');
     label.className = 'sticky-clock__label';
     label.className = 'sticky-clock__label';
     label.textContent = (CLOCK_TIMEZONE === 'Etc/UTC') ? 'UTC:' : 'Time:';
     label.textContent = 'UTC:';


     const value = document.createElement('span');
     const value = document.createElement('span');
Line 256: Line 228:
     wrap.appendChild(value);
     wrap.appendChild(value);


    // Put it near the front (use appendChild to put it at the end)
     const mid = getStickyTimeContainer();
     const center = getStickyCenterContainer();
     if (mid) mid.appendChild(wrap);
     if (center) center.appendChild(wrap);
    else icons.prepend(wrap); // fallback


     const fmt = new Intl.DateTimeFormat('en-GB', {
     const fmt = new Intl.DateTimeFormat('en-GB', {
Line 291: Line 261:
     'rgba(0, 29,  61,  0.8)',  // #001d3d
     'rgba(0, 29,  61,  0.8)',  // #001d3d
     'rgba(0,  8,  20,  0.7)',  // #000814
     'rgba(0,  8,  20,  0.7)',  // #000814
     'rgba(255, 255, 255, 0.15)', // subtle white glint
     'rgba(255, 255, 255, 0.15)' // subtle white glint
   ];
   ];


Line 313: Line 283:
       opacity: 0,
       opacity: 0,
       fadeIn:  randomBetween(0.003, 0.008),
       fadeIn:  randomBetween(0.003, 0.008),
       life:    randomBetween(0.4, 1),  // 0–1, fades out after 0.8
       life:    randomBetween(0.4, 1)
     };
     };
   }
   }
Line 319: Line 289:
   for (let i = 0; i < COUNT; i++) {
   for (let i = 0; i < COUNT; i++) {
     const p = createParticle();
     const p = createParticle();
     p.y = randomBetween(0, canvas.height); // spread on init
     p.y = randomBetween(0, canvas.height);
     p.opacity = randomBetween(0, 1);
     p.opacity = randomBetween(0, 1);
     particles.push(p);
     particles.push(p);
Line 328: Line 298:


     particles.forEach((p, i) => {
     particles.forEach((p, i) => {
      // Fade in / out
       if (p.life < 0.8) {
       if (p.life < 0.8) {
         p.opacity = Math.min(1, p.opacity + p.fadeIn);
         p.opacity = Math.min(1, p.opacity + p.fadeIn);
Line 339: Line 308:
       p.x    += p.speedX;
       p.x    += p.speedX;


      // Reset when off screen or fully faded
       if (p.y < -10 || p.life > 1.2) {
       if (p.y < -10 || p.life > 1.2) {
         particles[i] = createParticle();
         particles[i] = createParticle();
Line 351: Line 319:
       ctx.fill();
       ctx.fill();


      // Soft glow
       ctx.shadowBlur  = 8;
       ctx.shadowBlur  = 8;
       ctx.shadowColor = p.color;
       ctx.shadowColor = p.color;

Revision as of 09:58, 19 February 2026

/* 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: 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;

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

    // Put container at the start; CSS will center it
    icons.prepend(mid);

    return mid;
}

// =========================================================
// Add Custom Sticky Header Link (Discord) - unchanged
// =========================================================
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 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 = 'Ends in:';

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

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

    const mid = getStickyTimeContainer();
    if (mid) mid.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 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 `${months[monthIndex]} ${dayNumber} YR${year}`;
    }

    function update() {
        value.textContent = 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);
});

// =========================================================
// 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 mid = getStickyTimeContainer();
    if (mid) mid.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);
})();