Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Common.js: Difference between revisions

MediaWiki interface page
No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
 
(4 intermediate revisions by the same user not shown)
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();
} );
```
}() );
}() );