MediaWiki:Common.js
Appearance
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.
/* ============================================================
ZelocoreCMS Wiki β MediaWiki:Common.js
Animations, UX Enhancements, Interactive Features
============================================================ */
( function () {
'use strict';
/* ββ Utility βββββββββββββββββββββββββββββββββββββββββββ */
function qs( sel, ctx ) { return ( ctx || document ).querySelector( sel ); }
function qsa( sel, ctx ) { return [ ...( ctx || document ).querySelectorAll( sel ) ]; }
function ready( fn ) { if ( document.readyState !== 'loading' ) fn(); else document.addEventListener( 'DOMContentLoaded', fn ); }
/* ββ 1. Scroll-triggered Fade-in Animations ββββββββββββ */
function initScrollAnimations() {
var observer = new IntersectionObserver( function( entries ) {
entries.forEach( function( entry ) {
if ( entry.isIntersecting ) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
observer.unobserve( entry.target );
}
});
}, { threshold: 0.12 } );
qsa( '.zc-cms-card, .zc-category-box, .zc-stat-card, .zc-profile-card, .wikitable, h2, h3' )
.forEach( function( el ) {
el.style.opacity = '0';
el.style.transform = 'translateY(24px)';
el.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
observer.observe( el );
});
}
/* ββ 2. Animated Counter Numbers βββββββββββββββββββββββ */
function animateCounter( el, target, duration ) {
var start = 0;
var startTime = null;
var suffix = el.dataset.suffix || '';
function step( timestamp ) {
if ( !startTime ) startTime = timestamp;
var progress = Math.min( ( timestamp - startTime ) / duration, 1 );
var ease = 1 - Math.pow( 1 - progress, 3 ); // ease-out cubic
el.textContent = Math.floor( ease * target ).toLocaleString() + suffix;
if ( progress < 1 ) requestAnimationFrame( step );
}
requestAnimationFrame( step );
}
function initCounters() {
var counterObs = new IntersectionObserver( function( entries ) {
entries.forEach( function( entry ) {
if ( entry.isIntersecting ) {
var el = entry.target;
var val = parseInt( el.dataset.count, 10 );
animateCounter( el, val, 1800 );
counterObs.unobserve( el );
}
});
}, { threshold: 0.5 } );
qsa( '.zc-count' ).forEach( function( el ) { counterObs.observe( el ); } );
}
/* ββ 3. Enhanced Search Bar ββββββββββββββββββββββββββββ */
function initSearch() {
var input = qs( '#searchInput, .cdx-text-input__input, input[name="search"]' );
if ( !input ) return;
// Quick-search shortcuts (Ctrl+K or /)
document.addEventListener( 'keydown', function( e ) {
if ( ( e.ctrlKey || e.metaKey ) && e.key === 'k' ) {
e.preventDefault();
input.focus();
input.select();
}
if ( e.key === '/' && document.activeElement.tagName !== 'INPUT' &&
document.activeElement.tagName !== 'TEXTAREA' ) {
e.preventDefault();
input.focus();
}
} );
// Keyboard shortcut hint
if ( !qs( '.zc-search-hint' ) ) {
var hint = document.createElement( 'span' );
hint.className = 'zc-search-hint';
hint.textContent = 'Ctrl+K';
hint.style.cssText = [
'position:absolute', 'right:10px', 'top:50%', 'transform:translateY(-50%)',
'font-size:0.7rem', 'color:var(--zc-text-dim)',
'background:var(--zc-surface2)', 'border:1px solid var(--zc-border)',
'border-radius:4px', 'padding:1px 6px', 'pointer-events:none'
].join(';');
var wrap = input.closest( '.cdx-text-input, .mw-search-form, .vector-search-box' );
if ( wrap ) {
wrap.style.position = 'relative';
wrap.appendChild( hint );
}
}
}
/* ββ 4. Active Sidebar Link Highlight ββββββββββββββββββ */
function initSidebarHighlight() {
var currentPath = window.location.pathname;
qsa( '#mw-panel a, .mw-sidebar a, .vector-menu-content-list a' ).forEach( function( a ) {
if ( a.pathname && a.pathname !== '/' && currentPath.startsWith( a.pathname ) ) {
a.style.color = 'var(--zc-cyan)';
a.style.borderLeft = '2px solid var(--zc-cyan)';
a.style.background = 'var(--zc-cyan-dim)';
a.style.fontWeight = '600';
}
});
}
/* ββ 5. Code Block Copy Button βββββββββββββββββββββββββ */
function initCodeCopy() {
qsa( 'pre, .mw-highlight' ).forEach( function( block ) {
if ( qs( '.zc-copy-btn', block ) ) return;
var btn = document.createElement( 'button' );
btn.className = 'zc-copy-btn';
btn.textContent = 'β Copy';
btn.style.cssText = [
'position:absolute', 'top:10px', 'right:10px',
'background:var(--zc-surface2)', 'border:1px solid var(--zc-border)',
'color:var(--zc-text-muted)', 'font-size:0.75rem', 'font-weight:600',
'padding:3px 10px', 'border-radius:4px', 'cursor:pointer',
'transition:all 0.2s ease', 'font-family:var(--zc-font-body)'
].join(';');
btn.addEventListener( 'mouseenter', function() {
this.style.borderColor = 'var(--zc-cyan)';
this.style.color = 'var(--zc-cyan)';
});
btn.addEventListener( 'mouseleave', function() {
this.style.borderColor = 'var(--zc-border)';
this.style.color = 'var(--zc-text-muted)';
});
btn.addEventListener( 'click', function() {
var text = block.textContent.replace( /β Copy|β Copied/g, '' ).trim();
navigator.clipboard.writeText( text ).then( function() {
btn.textContent = 'β Copied';
btn.style.color = 'var(--zc-emerald)';
btn.style.borderColor = 'var(--zc-emerald)';
setTimeout( function() {
btn.textContent = 'β Copy';
btn.style.color = 'var(--zc-text-muted)';
btn.style.borderColor = 'var(--zc-border)';
}, 2000 );
});
});
block.style.position = 'relative';
block.appendChild( btn );
});
}
/* ββ 6. Smooth Anchor Scrolling with Offset ββββββββββββ */
function initSmoothScroll() {
qsa( 'a[href^="#"]' ).forEach( function( a ) {
a.addEventListener( 'click', function( e ) {
var target = qs( decodeURIComponent( this.hash ) );
if ( !target ) return;
e.preventDefault();
window.scrollTo( {
top: target.getBoundingClientRect().top + window.scrollY - 80,
behavior: 'smooth'
} );
});
});
}
/* ββ 7. TOC Sticky Scroll Spy ββββββββββββββββββββββββββ */
function initTOCSpy() {
var toc = qs( '#toc, .toc' );
if ( !toc ) return;
var headings = qsa( '.mw-body-content h2, .mw-body-content h3' );
var links = qsa( '#toc a, .toc a' );
if ( !headings.length || !links.length ) return;
window.addEventListener( 'scroll', mw.util.debounce( function() {
var scrollY = window.scrollY + 100;
var current = '';
headings.forEach( function( h ) {
if ( h.offsetTop <= scrollY ) current = '#' + h.id;
});
links.forEach( function( link ) {
link.style.color = 'var(--zc-text-muted)';
link.style.fontWeight = '400';
if ( link.getAttribute( 'href' ) === current ) {
link.style.color = 'var(--zc-cyan)';
link.style.fontWeight = '600';
}
});
}, 100 ) );
}
/* ββ 8. Developer Profile Tab Switcher βββββββββββββββββ */
function initProfileTabs() {
qsa( '.zc-tabs' ).forEach( function( tabGroup ) {
var tabs = qsa( '.zc-tab-btn', tabGroup );
var panels = qsa( '.zc-tab-panel', tabGroup );
tabs.forEach( function( tab, i ) {
tab.addEventListener( 'click', function() {
tabs.forEach( function( t ) {
t.style.color = 'var(--zc-text-muted)';
t.style.borderColor = 'transparent';
t.style.background = 'transparent';
});
panels.forEach( function( p ) { p.style.display = 'none'; } );
tab.style.color = 'var(--zc-cyan)';
tab.style.borderColor = 'var(--zc-cyan)';
tab.style.background = 'var(--zc-cyan-dim)';
if ( panels[ i ] ) panels[ i ].style.display = 'block';
});
});
if ( tabs[ 0 ] ) tabs[ 0 ].click();
});
}
/* ββ 9. Verified Badge Glow Pulse on Hover βββββββββββββ */
function initVerifiedHover() {
qsa( '.zc-verified-banner' ).forEach( function( el ) {
el.addEventListener( 'mouseenter', function() {
this.style.boxShadow = '0 0 40px rgba(0,232,143,0.5)';
});
el.addEventListener( 'mouseleave', function() {
this.style.boxShadow = '';
});
});
}
/* ββ 10. Reading Progress Bar ββββββββββββββββββββββββββ */
function initProgressBar() {
var bar = document.createElement( 'div' );
bar.id = 'zc-reading-progress';
bar.style.cssText = [
'position:fixed', 'top:0', 'left:0', 'height:2px', 'width:0%',
'background:linear-gradient(90deg, var(--zc-cyan), var(--zc-violet))',
'z-index:9999', 'transition:width 0.1s linear',
'box-shadow:0 0 8px rgba(0,200,255,0.6)'
].join(';');
document.body.appendChild( bar );
window.addEventListener( 'scroll', function() {
var docHeight = document.documentElement.scrollHeight - window.innerHeight;
var scrolled = ( window.scrollY / docHeight ) * 100;
bar.style.width = Math.min( scrolled, 100 ) + '%';
});
}
/* ββ 11. Back-to-Top Button ββββββββββββββββββββββββββββ */
function initBackToTop() {
var btn = document.createElement( 'button' );
btn.id = 'zc-back-to-top';
btn.textContent = 'β';
btn.title = 'Back to top';
btn.style.cssText = [
'position:fixed', 'bottom:28px', 'right:28px',
'width:42px', 'height:42px', 'border-radius:50%',
'background:linear-gradient(135deg,var(--zc-cyan),var(--zc-violet))',
'border:none', 'color:#000', 'font-size:1.2rem', 'font-weight:700',
'cursor:pointer', 'opacity:0', 'transition:opacity 0.3s ease, transform 0.3s ease',
'box-shadow:0 4px 20px rgba(0,200,255,0.4)', 'z-index:999'
].join(';');
document.body.appendChild( btn );
window.addEventListener( 'scroll', function() {
btn.style.opacity = window.scrollY > 400 ? '1' : '0';
btn.style.transform = window.scrollY > 400 ? 'scale(1)' : 'scale(0.8)';
});
btn.addEventListener( 'click', function() {
window.scrollTo( { top: 0, behavior: 'smooth' } );
});
}
/* ββ 12. Dark/Light mode toggle hint ββββββββββββββββββ */
// Wiki stays dark β but we persist the preference
function initTheme() {
document.documentElement.setAttribute( 'data-zc-theme', 'dark' );
}
/* ββ Bootstrap βββββββββββββββββββββββββββββββββββββββββ */
ready( function() {
initTheme();
initProgressBar();
initBackToTop();
initSearch();
initSidebarHighlight();
initScrollAnimations();
initCounters();
initSmoothScroll();
initTOCSpy();
initProfileTabs();
initVerifiedHover();
// Code copy needs a slight delay for SyntaxHighlight to render
setTimeout( initCodeCopy, 500 );
// Re-run code copy after VE switches back to read mode
mw.hook( 've.deactivate' ).add( function() {
setTimeout( initCodeCopy, 800 );
});
});
}() );