MediaWiki:Common.js: Difference between revisions
Appearance
Sonja says (talk | contribs) No edit summary |
Sonja says (talk | contribs) 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 | // Sticky header: centered TIME widgets container (SAFE) | ||
// =============================== | // - Centers ONLY the time widgets | ||
function | // - Leaves your Discord link exactly where it is | ||
const | // - Does NOT use absolute positioning | ||
if (! | // ========================================================= | ||
function getStickyTimeContainer() { | |||
const icons = document.querySelector('.vector-sticky-header-icons'); | |||
if (!icons) return null; | |||
let | let mid = document.getElementById('sticky-time-widgets'); | ||
if ( | if (mid) return mid; | ||
mid = document.createElement('span'); | |||
mid.id = 'sticky-time-widgets'; | |||
mid.className = 'sticky-time-widgets'; | |||
// Put | // Put container at the start; CSS will center it | ||
icons.prepend(mid); | |||
return | 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; | ||
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 = '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!'; | 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); | // Keep it exactly where you had it | ||
icons.prepend(link); | |||
}); | }); | ||
// =============================== | // ========================================================= | ||
// Sticky Header Countdown Timer ( | // 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; | ||
if (document.getElementById('sticky-countdown')) return; | if (document.getElementById('sticky-countdown')) return; | ||
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); | ||
const mid = getStickyTimeContainer(); | |||
const | if (mid) mid.appendChild(wrap); | ||
if ( | |||
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 diff = target.getTime() - Date.now(); | |||
const diff = target.getTime() - now | |||
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 | // 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 | ||
// | // 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']; | ||
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; // | const ANCHOR_DAY = 21; // 1..24 | ||
const ANCHOR_YEAR = 2; // YR2 | const ANCHOR_YEAR = 2; // YR2 | ||
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); | |||
const mid = getStickyTimeContainer(); | |||
const | if (mid) mid.appendChild(wrap); | ||
if ( | |||
function computeGameString() { | function computeGameString() { | ||
const hoursPassed = Math.floor((Date.now() - ANCHOR_REAL_UTC) / 3600000); | |||
const hoursPassed = Math.floor((now - ANCHOR_REAL_UTC) / 3600000); | |||
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; | ||
const daysPassed = Math.floor(dayIndex / 24); | const daysPassed = Math.floor(dayIndex / 24); | ||
const totalMonthIndex = ANCHOR_MONTH_INDEX + daysPassed; | const totalMonthIndex = ANCHOR_MONTH_INDEX + daysPassed; | ||
const monthIndex = ((totalMonthIndex % 7) + 7) % 7; | const monthIndex = ((totalMonthIndex % 7) + 7) % 7; | ||
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 | // 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 | // ========================================================= | ||
// =============================== | |||
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; | ||
const CLOCK_TIMEZONE = 'Etc/UTC'; | |||
const CLOCK_TIMEZONE = 'Etc/UTC'; | |||
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 | label.textContent = 'UTC:'; | ||
const value = document.createElement('span'); | const value = document.createElement('span'); | ||
| Line 256: | Line 228: | ||
wrap.appendChild(value); | wrap.appendChild(value); | ||
const mid = getStickyTimeContainer(); | |||
const | if (mid) mid.appendChild(wrap); | ||
if ( | |||
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)' | '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) | 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); | 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) => { | ||
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; | ||
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(); | ||
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);
})();