MediaWiki:Common.js: Difference between revisions

From LYC4NTHR0PYZ
No edit summary
Tag: Reverted
No edit summary
Tag: Manual revert
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();
} );
```
}() );
}() );

Revision as of 04:21, 20 March 2026

// == 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();
});