MediaWiki:Common.js: Difference between revisions
MediaWiki interface page
More actions
Mostknown663 (talk | contribs) No edit summary Tag: Manual revert |
Mostknown663 (talk | contribs) No edit summary Tag: Reverted |
||
| Line 178: | Line 178: | ||
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(); | |||
} ); | |||
``` | |||
}() ); | |||
}() ); | |||