Jump to content

MediaWiki:Common.js

From Utopia Game
Revision as of 21:25, 16 February 2026 by YBthr (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();
    });
});

// ===============================
// Add Custom Sticky Header Link
// ===============================
mw.hook('wikipage.content').add(function () {

    const icons = document.querySelector('.vector-sticky-header-icons');
    if (!icons) return;

    // Prevent duplicate button on navigation
    if (document.getElementById('custom-sticky-link')) return;

    const link = document.createElement('a');
    link.id = 'custom-sticky-link';
    link.href = 'https://discord.gg/t2Rp2dRvze'; // CHANGE THIS
    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!'; // CHANGE TEXT
    link.style.marginLeft = '10px';
    link.style.fontWeight = 'bold';

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

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

    // Prevent duplicates
    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 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);

    // Put it before the default icons (Talk/History/Edit)
    icons.prepend(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 now = new Date();
        const diff = target.getTime() - now.getTime();
        value.textContent = formatRemaining(diff);
        wrap.classList.toggle('is-expired', diff <= 0);
    };

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

    // Cleanup if element removed (rare, but safe)
    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
// Display: "Mar 1 YR1" (anchored at install time)
// Rules:
// - Each real hour = +1 in-game day number
// - Each 24 hours = +1 in-game month (Jan..Jul via 7-day cycle)
// - Each 7 months/days = +1 in-game year
// ===============================
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'];

    // --------- ANCHOR: "right now" is Mar 1 YR1 ----------
    const ANCHOR_MONTH_INDEX = 2; // Mar (Jan=0)
    const ANCHOR_DAY = 1;         // Day 1
    const ANCHOR_YEAR = 1;        // YR1

    // Optional timezone shift (0 = UTC). If you want server/local behavior, tell me your target TZ.
    const GAME_TZ_OFFSET_HOURS = 0;

    const getShiftedNow = () => new Date(Date.now() + GAME_TZ_OFFSET_HOURS * 3600000);

    // Anchor to the current real-hour boundary so changes happen cleanly on the hour
    const now = getShiftedNow();
    const anchorRealHour = new Date(Date.UTC(
        now.getUTCFullYear(),
        now.getUTCMonth(),
        now.getUTCDate(),
        now.getUTCHours(), 0, 0, 0
    ));

    // Build UI pill
    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);
    icons.prepend(wrap);

    function computeGameString() {
        const t = getShiftedNow();

        // Total whole hours passed since anchor
        const hoursPassed = Math.floor((t.getTime() - anchorRealHour.getTime()) / 3600000);

        // Day number advances each hour starting from ANCHOR_DAY
        const dayIndexFromAnchor = (ANCHOR_DAY - 1) + hoursPassed; // 0-based
        const dayNumber = (dayIndexFromAnchor % 24) + 1;

        // Every 24 hours, advance the "month/day-of-week" cycle
        const daysPassed = Math.floor(dayIndexFromAnchor / 24); // 0..∞

        // Total "day-of-week" index (0..6) across years
        const totalDayOfWeekIndex =
            (ANCHOR_YEAR - 1) * 7 + ANCHOR_MONTH_INDEX + daysPassed;

        const year = Math.floor(totalDayOfWeekIndex / 7) + 1;
        const month = months[((totalDayOfWeekIndex % 7) + 7) % 7];

        return `${month} ${dayNumber} YR${year}`;
    }

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

    // Update now
    update();

    // Update exactly on the next hour boundary, then every hour
    const now2 = getShiftedNow();
    const msUntilNextHour =
        3600000 - (now2.getUTCMinutes() * 60000 + now2.getUTCSeconds() * 1000 + now2.getUTCMilliseconds());

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


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

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

    // Choose the clock timezone:
    // - "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');
    wrap.id = 'sticky-clock';
    wrap.className = 'sticky-clock';

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

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

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

    // Put it near the front (use appendChild to put it at the end)
    icons.prepend(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);
});


/**
 * Utopia Wiki Header Animations - MediaWiki Compatible
 * Direct targeting approach for MediaWiki 1.45+
 */

$(document).ready(function() {
    'use strict';
    
    // Find the image first as our anchor point
    var $logo = $('img[src*="Utopiawiki"]').first();
    
    if ($logo.length === 0) {
        console.log('Utopia logo not found');
        return;
    }
    
    console.log('Utopia animations: Logo found, initializing...');
    
    // Find the container - go up until we find the div with padding
    var $container = $logo.closest('div[style*="padding"]');
    
    if ($container.length === 0) {
        // Try alternative: find parent div
        $container = $logo.parent().parent();
    }
    
    console.log('Container found:', $container.length);
    
    // Wrap everything in our animation container if not already wrapped
    if (!$container.hasClass('utopia-header-container')) {
        $container.addClass('utopia-header-container');
        $container.css({
            'position': 'relative',
            'overflow': 'hidden',
            'background': 'linear-gradient(135deg, rgba(15, 15, 35, 0.95) 0%, rgba(30, 30, 60, 0.95) 100%)',
            'border-radius': '12px',
            'box-shadow': '0 8px 32px rgba(0, 0, 0, 0.4)',
            'padding': '3em 2em'
        });
    }
    
    // Style the logo
    $logo.addClass('utopia-logo').css({
        'filter': 'drop-shadow(0 4px 12px rgba(255, 195, 0, 0.3))',
        'transition': 'transform 0.3s ease, filter 0.3s ease'
    });
    
    // Find and style text divs
    $container.find('div').each(function() {
        var $div = $(this);
        var text = $div.text().trim();
        
        if (text.includes('Comprehensive Guide')) {
            $div.addClass('utopia-subtitle');
            console.log('Added subtitle class');
        } else if (text.includes('Welcome to')) {
            $div.addClass('utopia-welcome');
            console.log('Added welcome class');
        } else if (text.includes('Age')) {
            $div.addClass('utopia-ages');
            console.log('Added ages class');
        }
    });
    
    // Style all links in the ages section
    $container.find('a').each(function(index) {
        var $link = $(this);
        $link.addClass('age-link');
        
        // First link is current
        if (index === 0) {
            $link.addClass('age-current');
        }
        
        // Wrap in a styled container for better effect
        $link.css({
            'display': 'inline-block',
            'padding': '0.8em 1.5em',
            'margin': '0.5em',
            'background': 'linear-gradient(135deg, rgba(255, 195, 0, 0.1) 0%, rgba(255, 195, 0, 0.05) 100%)',
            'border': '2px solid rgba(255, 195, 0, 0.3)',
            'border-radius': '8px',
            'transition': 'all 0.3s ease',
            'position': 'relative'
        });
    });
    
    // Add hover effects to links
    $container.on('mouseenter', '.age-link', function() {
        $(this).css({
            'transform': 'translateY(-4px)',
            'border-color': 'rgba(255, 195, 0, 0.6)',
            'background': 'linear-gradient(135deg, rgba(255, 195, 0, 0.2) 0%, rgba(255, 195, 0, 0.1) 100%)',
            'box-shadow': '0 8px 24px rgba(255, 195, 0, 0.3)'
        });
        
        $(this).find('span').css({
            'text-shadow': '0 0 12px rgba(255, 195, 0, 0.8)'
        });
    }).on('mouseleave', '.age-link', function() {
        $(this).css({
            'transform': 'translateY(0)',
            'border-color': 'rgba(255, 195, 0, 0.3)',
            'background': 'linear-gradient(135deg, rgba(255, 195, 0, 0.1) 0%, rgba(255, 195, 0, 0.05) 100%)',
            'box-shadow': 'none'
        });
        
        $(this).find('span').css({
            'text-shadow': 'none'
        });
    });
    
    // Add floating particles
    addFloatingParticles($container);
    
    // Add parallax effect to logo
    addParallaxEffect($container, $logo);
    
    // Add animations
    animateElements($container);
    
    console.log('Utopia animations initialized successfully!');
    
    function addFloatingParticles($container) {
        // Create particle container
        var $particleContainer = $('<div class="utopia-particles"></div>').css({
            'position': 'absolute',
            'top': '0',
            'left': '0',
            'width': '100%',
            'height': '100%',
            'pointer-events': 'none',
            'overflow': 'hidden',
            'z-index': '1'
        });
        
        $container.prepend($particleContainer);
        
        // Create particles
        for (var i = 0; i < 15; i++) {
            createParticle($particleContainer);
        }
    }
    
    function createParticle($container) {
        var size = Math.random() * 3 + 1;
        var duration = Math.random() * 15 + 10;
        var delay = Math.random() * 5;
        var startX = Math.random() * 100;
        var endX = startX + (Math.random() * 100 - 50);
        
        var $particle = $('<div class="particle"></div>').css({
            'position': 'absolute',
            'width': size + 'px',
            'height': size + 'px',
            'background': 'rgba(255, 195, 0, ' + (Math.random() * 0.5 + 0.2) + ')',
            'border-radius': '50%',
            'left': startX + '%',
            'bottom': '-10px',
            'box-shadow': '0 0 ' + (size * 2) + 'px rgba(255, 195, 0, 0.5)',
            'opacity': '0'
        });
        
        $container.append($particle);
        
        // Animate particle
        setTimeout(function() {
            animateParticle($particle, duration, endX);
        }, delay * 1000);
    }
    
    function animateParticle($particle, duration, endX) {
        $particle.css({
            'transition': 'all ' + duration + 's linear',
            'transform': 'translateY(-100vh) translateX(' + endX + 'px) rotate(360deg)',
            'opacity': '1'
        });
        
        // Restart animation
        setTimeout(function() {
            $particle.css({
                'transition': 'none',
                'transform': 'translateY(0) translateX(0) rotate(0deg)',
                'opacity': '0'
            });
            
            setTimeout(function() {
                animateParticle($particle, duration, endX);
            }, 100);
        }, duration * 1000);
    }
    
    function addParallaxEffect($container, $logo) {
        var mouseX = 0;
        var mouseY = 0;
        var currentX = 0;
        var currentY = 0;
        
        $container.on('mousemove', function(e) {
            var offset = $container.offset();
            var width = $container.width();
            var height = $container.height();
            
            mouseX = (e.pageX - offset.left - width / 2) / width;
            mouseY = (e.pageY - offset.top - height / 2) / height;
        });
        
        $container.on('mouseleave', function() {
            mouseX = 0;
            mouseY = 0;
        });
        
        function animate() {
            currentX += (mouseX - currentX) * 0.1;
            currentY += (mouseY - currentY) * 0.1;
            
            $logo.css('transform', 'translate(' + (currentX * 20) + 'px, ' + (currentY * 20) + 'px)');
            
            requestAnimationFrame(animate);
        }
        
        animate();
    }
    
    function animateElements($container) {
        // Animate subtitle
        var $subtitle = $container.find('.utopia-subtitle');
        if ($subtitle.length) {
            $subtitle.css({
                'opacity': '0',
                'transform': 'translateY(30px)'
            });
            
            setTimeout(function() {
                $subtitle.css({
                    'transition': 'all 1s ease-out',
                    'opacity': '1',
                    'transform': 'translateY(0)',
                    'text-shadow': '0 2px 8px rgba(255, 255, 255, 0.2)'
                });
            }, 300);
        }
        
        // Animate welcome
        var $welcome = $container.find('.utopia-welcome');
        if ($welcome.length) {
            $welcome.css({
                'opacity': '0',
                'transform': 'translateY(30px)'
            });
            
            setTimeout(function() {
                $welcome.css({
                    'transition': 'all 1.2s ease-out',
                    'opacity': '1',
                    'transform': 'translateY(0)'
                });
                
                // Animate the bold Utopia text
                $welcome.find('b, strong').css({
                    'color': '#FFC300',
                    'text-shadow': '0 0 10px rgba(255, 195, 0, 0.4)'
                });
            }, 600);
        }
        
        // Animate ages
        var $ages = $container.find('.utopia-ages');
        if ($ages.length) {
            $ages.css({
                'opacity': '0',
                'transform': 'translateY(30px)'
            });
            
            setTimeout(function() {
                $ages.css({
                    'transition': 'all 1.4s ease-out',
                    'opacity': '1',
                    'transform': 'translateY(0)'
                });
            }, 900);
        }
        
        // Float animation for logo
        floatLogo($container.find('.utopia-logo'));
        
        // Pulse animation for current age
        pulseCurrentAge($container.find('.age-current'));
    }
    
    function floatLogo($logo) {
        var up = true;
        
        setInterval(function() {
            if (up) {
                $logo.css('transform', 'translateY(-15px)');
            } else {
                $logo.css('transform', 'translateY(0)');
            }
            up = !up;
        }, 3000);
        
        $logo.css('transition', 'transform 3s ease-in-out');
    }
    
    function pulseCurrentAge($current) {
        if ($current.length === 0) return;
        
        setInterval(function() {
            $current.css('box-shadow', '0 0 25px rgba(255, 195, 0, 0.6), 0 0 35px rgba(255, 195, 0, 0.3)');
            
            setTimeout(function() {
                $current.css('box-shadow', '0 0 15px rgba(255, 195, 0, 0.3)');
            }, 1000);
        }, 2000);
        
        $current.css('transition', 'box-shadow 1s ease-in-out');
    }
});