MediaWiki:Common.js
From LYC4NTHR0PYZ
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.
// == Confetti Effect ==
( function () {
'use strict';
var canvas, ctx, particles, animId;
function random( min, max ) {
return Math.random() * ( max - min ) + min;
}
function createParticle() {
return {
x: random( 0, window.innerWidth ),
y: random( -20, 0 ),
w: random( 7, 14 ),
h: random( 4, 8 ),
color: 'hsl(' + Math.floor( random( 0, 360 ) ) + ',90%,60%)',
speed: random( 2, 6 ),
angle: random( -0.4, 0.4 ),
spin: random( -0.15, 0.15 ),
rot: random( 0, Math.PI * 2 ),
wobble: random( 0, Math.PI * 2 ),
wobbleSpeed: random( 0.05, 0.12 )
};
}
function launch( count ) {
canvas = document.createElement( 'canvas' );
canvas.style.cssText =
'position:fixed;top:0;left:0;width:100%;height:100%;' +
'pointer-events:none;z-index:99999;';
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild( canvas );
ctx = canvas.getContext( '2d' );
particles = [];
for ( var i = 0; i < ( count || 150 ); i++ ) {
particles.push( createParticle() );
}
function step() {
ctx.clearRect( 0, 0, canvas.width, canvas.height );
var alive = false;
for ( var j = 0; j < particles.length; j++ ) {
var p = particles[ j ];
p.y += p.speed;
p.x += Math.sin( p.wobble ) * 1.5;
p.wobble += p.wobbleSpeed;
p.rot += p.spin;
if ( p.y < canvas.height + 20 ) {
alive = true;
ctx.save();
ctx.translate( p.x, p.y );
ctx.rotate( p.rot );
ctx.fillStyle = p.color;
ctx.fillRect( -p.w / 2, -p.h / 2, p.w, p.h );
ctx.restore();
}
}
if ( alive ) {
animId = requestAnimationFrame( step );
} else {
cancelAnimationFrame( animId );
canvas.parentNode.removeChild( canvas );
}
}
step();
window.addEventListener( 'resize', function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
} );
}
// Fire confetti when the DOM is ready
mw.loader.using( 'mediawiki.util' ).done( function () {
launch( 150 );
} );
}() );
mw.hook('wikipage.content').add(function() {
// Add class "rainbow-text" to any element you want animated
document.querySelectorAll('.rainbow-text').forEach(function(el) {
let hue = 0;
setInterval(function() {
el.style.color = 'hsl(' + hue + ', 100%, 50%)';
hue = (hue + 2) % 360;
}, 20);
});
});
mw.hook('wikipage.content').add(function() {
document.querySelectorAll('.glitch-text').forEach(function(el) {
const original = el.textContent;
const glitchChars = '!<>-_\\/[]{}—=+*^?#@$%&';
setInterval(function() {
if (Math.random() > 0.85) { // Trigger glitch ~15% of the time
let glitched = '';
for (let i = 0; i < original.length; i++) {
if (Math.random() > 0.8) {
glitched += glitchChars[Math.floor(Math.random() * glitchChars.length)];
} else {
glitched += original[i];
}
}
el.textContent = glitched;
setTimeout(function() {
el.textContent = original; // Restore after brief glitch
}, 80);
}
}, 150);
});
});
mw.hook('wikipage.content').add(function() {
document.querySelectorAll('.rainbow-glitch').forEach(function(el) {
const original = el.textContent;
const glitchChars = '!<>-_\\/[]{}—=+*^?#@$%&';
let hue = 0;
// Rainbow cycling
setInterval(function() {
el.style.color = 'hsl(' + hue + ', 100%, 55%)';
el.style.textShadow = '2px 0 hsl(' + ((hue + 120) % 360) + ', 100%, 50%), -2px 0 hsl(' + ((hue + 240) % 360) + ', 100%, 50%)';
hue = (hue + 3) % 360;
}, 30);
// Glitch effect
setInterval(function() {
if (Math.random() > 0.85) {
let glitched = '';
for (let i = 0; i < original.length; i++) {
glitched += Math.random() > 0.75
? glitchChars[Math.floor(Math.random() * glitchChars.length)]
: original[i];
}
el.textContent = glitched;
setTimeout(() => { el.textContent = original; }, 100);
}
}, 200);
});
});
mw.hook('wikipage.content').add(function() {
const canvas = document.createElement('canvas');
canvas.style.cssText = 'position:fixed;top:0;left:0;pointer-events:none;z-index:9999';
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const flakes = Array.from({length: 100}, () => ({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
r: Math.random() * 4 + 1,
speed: Math.random() * 1.5 + 0.5
}));
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
flakes.forEach(f => {
ctx.beginPath();
ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2);
ctx.fill();
f.y += f.speed;
if (f.y > canvas.height) f.y = 0;
});
requestAnimationFrame(draw);
}
draw();
});
/* =============================================================
MediaWiki Fun JS — Drop into MediaWiki:Common.js
Includes:
1. ✨ Cursor Sparkle Trail
2. 🎮 Konami Code Easter Egg
3. 📜 Reading Progress Bar
============================================================= */
( function () {
‘use strict’;
/* ———————————————————–
SHARED UTILITY: run after DOM is ready
———————————————————– */
function onReady( fn ) {
if ( document.readyState !== ‘loading’ ) { fn(); }
else { document.addEventListener( ‘DOMContentLoaded’, fn ); }
}
/* ===========================================================
1. ✨ CURSOR SPARKLE TRAIL
Spawns little coloured star/sparkle particles that follow
the mouse, drift upward, and fade out.
=========================================================== */
( function sparkleTrail() {
var SPARKLES = [ ‘✦’, ‘✧’, ‘⋆’, ‘sparkle’, ‘·’, ‘✸’, ‘✹’ ];
var COLORS = [ ‘#ffd700’, ‘#ff69b4’, ‘#00cfff’, ‘#b8ff6e’, ‘#ff9f43’, ‘#a29bfe’, ‘#fd79a8’ ];
var MAX_PARTICLES = 60; // cap to avoid perf issues
var active = 0;
```
/* Inject base styles once */
var style = document.createElement( 'style' );
style.textContent = [
'.mw-sparkle {',
' position: fixed;',
' pointer-events: none;',
' font-size: 14px;',
' line-height: 1;',
' user-select: none;',
' z-index: 99999;',
' animation: mw-sparkle-fade 0.9s ease-out forwards;',
'}',
'@keyframes mw-sparkle-fade {',
' 0% { opacity: 1; transform: translate(0, 0) scale(1); }',
' 100% { opacity: 0; transform: translate(var(--sx), var(--sy)) scale(0.3); }',
'}'
].join( '\n' );
document.head.appendChild( style );
function spawnSparkle( x, y ) {
if ( active >= MAX_PARTICLES ) { return; }
active++;
var el = document.createElement( 'span' );
var glyph = SPARKLES[ Math.floor( Math.random() * SPARKLES.length ) ];
// "sparkle" is the keyword — map it to the ✨ emoji
el.textContent = ( glyph === 'sparkle' ) ? '✨' : glyph;
el.className = 'mw-sparkle';
el.style.color = COLORS[ Math.floor( Math.random() * COLORS.length ) ];
el.style.left = ( x - 8 + ( Math.random() * 16 - 8 ) ) + 'px';
el.style.top = ( y - 8 + ( Math.random() * 16 - 8 ) ) + 'px';
/* Random drift direction */
var driftX = ( Math.random() * 60 - 30 ) + 'px';
var driftY = '-' + ( 30 + Math.random() * 50 ) + 'px';
el.style.setProperty( '--sx', driftX );
el.style.setProperty( '--sy', driftY );
document.body.appendChild( el );
el.addEventListener( 'animationend', function () {
el.remove();
active--;
} );
}
/* Throttle: max one sparkle per ~30 ms */
var lastSparkle = 0;
document.addEventListener( 'mousemove', function ( e ) {
var now = Date.now();
if ( now - lastSparkle < 30 ) { return; }
lastSparkle = now;
spawnSparkle( e.clientX, e.clientY );
} );
```
}() );
/* ===========================================================
2. 🎮 KONAMI CODE EASTER EGG
↑ ↑ ↓ ↓ ← → ← → B A
Triggers a full-screen emoji rain for ~4 seconds.
=========================================================== */
( function konamiCode() {
var SEQUENCE = [ 38, 38, 40, 40, 37, 39, 37, 39, 66, 65 ];
var EMOJIS = [ ‘🎮’, ‘⭐’, ‘🌟’, ‘💥’, ‘🎉’, ‘🏆’, ‘🕹️’, ‘👾’, ‘🎊’, ‘🦄’, ‘🍄’, ‘💫’ ];
var progress = 0;
var running = false;
```
/* Styles */
var style = document.createElement( 'style' );
style.textContent = [
'.mw-konami-overlay {',
' position: fixed; inset: 0;',
' pointer-events: none;',
' z-index: 99998;',
' overflow: hidden;',
'}',
'.mw-konami-drop {',
' position: absolute;',
' top: -60px;',
' font-size: 32px;',
' animation: mw-konami-fall linear forwards;',
' user-select: none;',
' pointer-events: none;',
'}',
'@keyframes mw-konami-fall {',
' to { transform: translateY( 110vh ) rotate( 360deg ); opacity: 0.2; }',
'}',
'.mw-konami-banner {',
' position: fixed;',
' top: 50%;',
' left: 50%;',
' transform: translate(-50%, -50%) scale(0);',
' background: linear-gradient(135deg, #2d3436, #6c5ce7);',
' color: #fff;',
' font-family: monospace;',
' font-size: clamp(18px, 4vw, 36px);',
' font-weight: bold;',
' padding: 1em 2em;',
' border-radius: 12px;',
' z-index: 100000;',
' pointer-events: none;',
' animation: mw-banner-pop 0.4s cubic-bezier(0.34,1.56,0.64,1) 0.1s forwards,',
' mw-banner-fade 0.6s ease-in 3.4s forwards;',
' box-shadow: 0 8px 32px rgba(0,0,0,0.5);',
' text-align: center;',
' white-space: nowrap;',
'}',
'@keyframes mw-banner-pop { to { transform: translate(-50%, -50%) scale(1); } }',
'@keyframes mw-banner-fade { to { opacity: 0; transform: translate(-50%, -60%) scale(0.9); } }'
].join( '\n' );
document.head.appendChild( style );
function triggerEasterEgg() {
if ( running ) { return; }
running = true;
/* Banner */
var banner = document.createElement( 'div' );
banner.className = 'mw-konami-banner';
banner.textContent = '🎮 KONAMI CODE ACTIVATED 🎮';
document.body.appendChild( banner );
/* Emoji rain overlay */
var overlay = document.createElement( 'div' );
overlay.className = 'mw-konami-overlay';
document.body.appendChild( overlay );
var dropCount = 0;
var maxDrops = 80;
var duration = 4000; /* ms total rain */
var interval = duration / maxDrops;
var timer = setInterval( function () {
if ( dropCount >= maxDrops ) {
clearInterval( timer );
return;
}
dropCount++;
var drop = document.createElement( 'span' );
drop.className = 'mw-konami-drop';
drop.textContent = EMOJIS[ Math.floor( Math.random() * EMOJIS.length ) ];
drop.style.left = ( Math.random() * 100 ) + 'vw';
var fallDuration = ( 1.5 + Math.random() * 2.5 ).toFixed(2) + 's';
drop.style.animationDuration = fallDuration;
overlay.appendChild( drop );
drop.addEventListener( 'animationend', function () { drop.remove(); } );
}, interval );
/* Clean up after 5 s */
setTimeout( function () {
overlay.remove();
banner.remove();
running = false;
}, 5000 );
}
document.addEventListener( 'keydown', function ( e ) {
if ( e.keyCode === SEQUENCE[ progress ] ) {
progress++;
if ( progress === SEQUENCE.length ) {
progress = 0;
triggerEasterEgg();
}
} else {
progress = ( e.keyCode === SEQUENCE[ 0 ] ) ? 1 : 0;
}
} );
```
}() );
/* ===========================================================
3. 📜 READING PROGRESS BAR
Thin bar pinned to the top of the viewport that fills as
the user scrolls through the article content.
=========================================================== */
( function readingProgress() {
var style = document.createElement( ‘style’ );
style.textContent = [
‘#mw-progress-track {’,
’ position: fixed;’,
’ top: 0; left: 0;’,
’ width: 100%;’,
’ height: 3px;’,
’ background: rgba(0,0,0,0.08);’,
’ z-index: 99997;’,
’ pointer-events: none;’,
‘}’,
‘#mw-progress-fill {’,
’ height: 100%;’,
’ width: 0%;’,
’ background: linear-gradient(90deg, #6c5ce7, #00cec9, #fd79a8);’,
’ background-size: 200% 100%;’,
’ transition: width 0.1s linear;’,
’ animation: mw-progress-shimmer 3s linear infinite;’,
‘}’,
‘@keyframes mw-progress-shimmer {’,
’ 0% { background-position: 200% 0; }’,
’ 100% { background-position: -200% 0; }’,
‘}’
].join( ‘\n’ );
document.head.appendChild( style );
```
onReady( function () {
var track = document.createElement( 'div' );
track.id = 'mw-progress-track';
var fill = document.createElement( 'div' );
fill.id = 'mw-progress-fill';
track.appendChild( fill );
document.body.insertBefore( track, document.body.firstChild );
function updateProgress() {
/* Use the article content node if available, else full document */
var content = document.getElementById( 'mw-content-text' ) || document.body;
var rect = content.getBoundingClientRect();
var total = rect.height - window.innerHeight;
var scrolled = -rect.top;
var pct = total > 0 ? Math.min( 100, Math.max( 0, ( scrolled / total ) * 100 ) ) : 0;
fill.style.width = pct.toFixed( 2 ) + '%';
}
window.addEventListener( 'scroll', updateProgress, { passive: true } );
window.addEventListener( 'resize', updateProgress, { passive: true } );
updateProgress();
} );
```
}() );
}() );