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