Jump to content

MediaWiki:Common.js

From ZelocoreCMS Wiki
Revision as of 22:11, 28 July 2026 by Digiwayen (talk | contribs) (ZelocoreCMS JS enhancements β€” initial deploy)
(diff) ← Older revision | Latest revision (diff) | Newer revision β†’ (diff)

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

}() );