MediaWiki:Common.js: Difference between revisions

No edit summary
No edit summary
Line 91: Line 91:
       'क':'க','ख':'க','ग':'க','घ':'க','ङ':'ங',
       'क':'க','ख':'க','ग':'க','घ':'க','ङ':'ங',
       'च':'ச','छ':'ச','ज':'ஜ','झ':'ஜ','ञ':'ஞ',
       'च':'ச','छ':'ச','ज':'ஜ','झ':'ஜ','ञ':'ஞ',
       'ट':'ட','ठ':'ட','ड':'ட','ढ':'ட','':'ண',
       'ट':'ட','ठ':'ட','ड':'ட','ढ':'ட','':'ண',
       'त':'த','थ':'த','द':'த','ध':'த','न':'ந',
       'त':'த','थ':'த','द':'த','ध':'த','न':'ந',
       'प':'ப','फ':'ப','ब':'ப','भ':'ப','म':'ம',
       'प':'ப','फ':'ப','ब':'ப','भ':'ப','म':'ம',
       'य':'ய','र':'ர','':'ல','ळ':'ழ','व':'வ',
       'य':'ய','र':'ர','':'ல','ळ':'ழ','व':'வ',
       'श':'ஶ','ष':'ஷ','स':'ஸ','ह':'ஹ',
       'श':'ஶ','ष':'ஷ','स':'ஸ','ह':'ஹ',
       'ा':'ா','ि':'ி','ी':'ீ','':'ு','ू':'ூ',
       'ा':'ா','ि':'ி','ी':'ீ','':'ு','ू':'ூ',
       'ृ':'ு','ॄ':'ூ',
       'ृ':'ு','ॄ':'ூ',
       'े':'ே','':'ை','ो':'ோ','ौ':'ௌ',
       'े':'ே','':'ை','ो':'ோ','ौ':'ௌ',
       'ं':'ம்','ः':':','ँ':'ம்','्':'்','ॐ':'ௐ','ऽ':'ௗ',
       'ं':'ம்','ः':':','ँ':'ம்','्':'்','ॐ':'ௐ','ऽ':'ௗ',
       '०':'0','१':'1','२':'2','३':'3','४':'4',
       '०':'0','१':'1','२':'2','३':'3','४':'4',
Line 139: Line 139:
           if ( p.closest( '.gr-controls' )    ) return;
           if ( p.closest( '.gr-controls' )    ) return;
           if ( p.closest( '.mw-editsection' ) ) return;
           if ( p.closest( '.mw-editsection' ) ) return;
          // #gr-toc-doc-nav buttons manage their own data-deva spans in makeBtn
         }
         }
         var orig = node.textContent;
         var orig = node.textContent;
Line 194: Line 193:
   var LABEL = 'विषयसूची';
   var LABEL = 'विषयसूची';


  /* If already inserted, just refresh text */
   var span = titleEl.querySelector('.gr-toc-title');
   var span = titleEl.querySelector('.gr-toc-title');


Line 205: Line 203:


     titleEl.appendChild(span);
     titleEl.appendChild(span);
     translatableSpans.push(span);   // uses your internal array
     translatableSpans.push(span);
   }
   }


Line 218: Line 216:
     var toc = document.querySelector( '.vector-toc' );
     var toc = document.querySelector( '.vector-toc' );
     if ( !toc ) return;
     if ( !toc ) return;
    // Try the dedicated id first (Vector 2022)
     var el = toc.querySelector( '#vector-toc-beginning' );
     var el = toc.querySelector( '#vector-toc-beginning' );
     if ( !el ) {
     if ( !el ) {
      // Fallback: first list-item whose link has no # anchor = the "Beginning" entry
       var items = toc.querySelectorAll( '.vector-toc-list-item' );
       var items = toc.querySelectorAll( '.vector-toc-list-item' );
       for ( var i = 0; i < items.length; i++ ) {
       for ( var i = 0; i < items.length; i++ ) {
Line 288: Line 284:
     nav.setAttribute( 'class', 'toc-main-links');
     nav.setAttribute( 'class', 'toc-main-links');


    // ── CHANGE: makeBtn now wraps label in a data-deva span so
    // transliteration (script switching) applies to button text.
     function makeBtn( href, label ) {
     function makeBtn( href, label ) {
       var a = document.createElement( 'a' );
       var a = document.createElement( 'a' );
Line 448: Line 442:


   // ── TOC: Use Vector's native TOC, add highlight + expand + rename ──────
   // ── TOC: Use Vector's native TOC, add highlight + expand + rename ──────
  // Drops the custom TOC builder entirely. Vector builds the correct TOC
  // from page headings (== Chapter ==, === Section ===). We just:
  //  1. Rename "Contents" → "विषयसूची"
  //  2. Expand all collapsed sections
  //  3. Remove the "Beginning" top link
  //  4. Inject मूल/उल्लेख nav
  //  5. Add reliable IntersectionObserver active-highlight on headings
   function setupToc() {
   function setupToc() {
     if ( _isNoTocPage() ) return;
     if ( _isNoTocPage() ) return;
Line 469: Line 455:


   // ── IntersectionObserver on actual heading elements ──────────────
   // ── IntersectionObserver on actual heading elements ──────────────
  // Watches the real == Heading == elements in the page body.
  // Much more reliable than watching gr-toc-anchor spans.
   var _headingObserver = null;
   var _headingObserver = null;


   function attachHeadingObserver() {
   function attachHeadingObserver() {
     if ( _isNoTocPage() ) return;
     if ( _isNoTocPage() ) return;
     if ( _headingObserver ) return;   // only attach once
     if ( _headingObserver ) return;
     if ( !window.IntersectionObserver ) {
     if ( !window.IntersectionObserver ) {
       watchTocActive();               // fallback for old browsers
       return; /* IntersectionObserver not available — skip active highlight */
      return;
     }
     }


Line 484: Line 467:
     var _activeId    = null;
     var _activeId    = null;


    // Collect all headings that have an id (Vector gives them ids)
     var content  = document.querySelector( '.mw-parser-output' );
     var content  = document.querySelector( '.mw-parser-output' );
     if ( !content ) return;
     if ( !content ) return;
Line 496: Line 478:


     function getTocLink( id ) {
     function getTocLink( id ) {
      // Vector renders TOC links as href="#id"
       return toc.querySelector( 'a[href="#' + CSS.escape( id ) + '"]' );
       return toc.querySelector( 'a[href="#' + CSS.escape( id ) + '"]' );
     }
     }
Line 506: Line 487:


     function clearActive() {
     function clearActive() {
       toc.querySelectorAll( '.vector-toc-list-item-active' ).forEach( function ( li ) {
      /* Clear ALL toc items — prevents multiple items staying highlighted simultaneously */
       toc.querySelectorAll( '.vector-toc-list-item' ).forEach( function ( li ) {
         li.classList.remove( 'vector-toc-list-item-active' );
         li.classList.remove( 'vector-toc-list-item-active' );
         var lnk = li.querySelector( '.vector-toc-link' );
         var lnk = li.querySelector( '.vector-toc-link' );
Line 530: Line 512:
       li.classList.add( 'vector-toc-list-item-active' );
       li.classList.add( 'vector-toc-list-item-active' );


      // Highlight only the innermost active item
       var hasActiveChild = !!li.querySelector(
       var hasActiveChild = !!li.querySelector(
         '.vector-toc-list-item .vector-toc-list-item-active'
         '.vector-toc-list-item .vector-toc-list-item-active'
Line 546: Line 527:
       }
       }


       // Expand collapsed ancestors
       // Expand ALL ancestor sections — remove collapsed, force display
       var anc = li.parentNode;
       var anc = li.parentNode;
       while ( anc && anc !== toc ) {
       while ( anc && anc !== toc ) {
         if ( anc.classList && anc.classList.contains( 'vector-toc-list-item-collapsed' ) ) {
         if ( anc.classList ) {
           anc.classList.remove( 'vector-toc-list-item-collapsed' );
           anc.classList.remove( 'vector-toc-list-item-collapsed' );
        }
        if ( anc.tagName === 'UL' || anc.tagName === 'LI' ) {
          anc.style.removeProperty( 'display' );
         }
         }
         anc = anc.parentNode;
         anc = anc.parentNode;
Line 567: Line 551:
     }
     }


    // Track which headings are visible
     var _visible = new Set();
     var _visible = new Set();


Line 579: Line 562:
       } );
       } );


      // Pick the topmost visible heading
       var topId = null;
       var topId = null;
       var topY  = Infinity;
       var topY  = Infinity;
Line 590: Line 572:
       } );
       } );


      // Nothing visible: find the last heading scrolled past (above viewport)
       if ( !topId ) {
       if ( !topId ) {
         var bestY = -Infinity;
         var bestY = -Infinity;
Line 601: Line 582:
       setActive( topId || null );
       setActive( topId || null );
     }, {
     }, {
      // Fire when heading enters/leaves the top 30% of the viewport
       rootMargin: '-60px 0px -65% 0px',
       rootMargin: '-60px 0px -65% 0px',
       threshold: 0
       threshold: 0
Line 677: Line 657:
     else { currentScript = 'deva'; }
     else { currentScript = 'deva'; }


    // Vector 2022 defers TOC render — retry at 300ms and 800ms
    // Use a single short delay so Vector has rendered the TOC
     setTimeout( setupToc, 200 );
     setTimeout( setupToc, 200 );
   }
   }
Line 689: Line 667:


   // ── React to gr-new-content (siteNav panel rendered new items) ──
   // ── React to gr-new-content (siteNav panel rendered new items) ──
  // Tag any new text nodes added by the documents panel
   window.addEventListener( 'gr-new-content', function ( e ) {
   window.addEventListener( 'gr-new-content', function ( e ) {
     var container = e && e.detail && e.detail.container;
     var container = e && e.detail && e.detail.container;
Line 859: Line 836:
     if ( !needle || needle.length < 4 ) return;
     if ( !needle || needle.length < 4 ) return;


    // Wait for tagTextNodes() to finish wrapping text in data-deva spans,
    // then search those spans rather than raw text nodes.
     function doHighlight() {
     function doHighlight() {
       var content = document.querySelector( '.mw-parser-output' );
       var content = document.querySelector( '.mw-parser-output' );
Line 868: Line 843:
       var found  = false;
       var found  = false;


      // Search data-deva spans first (post-tagTextNodes state)
       var spans = content.querySelectorAll( '[data-deva]' );
       var spans = content.querySelectorAll( '[data-deva]' );
       for ( var i = 0; i < spans.length && !found; i++ ) {
       for ( var i = 0; i < spans.length && !found; i++ ) {
Line 875: Line 849:
         if ( orig.indexOf( snippet ) === -1 ) continue;
         if ( orig.indexOf( snippet ) === -1 ) continue;


        // Replace the span with: text-before + <mark> + text-after
         var idx    = orig.indexOf( snippet );
         var idx    = orig.indexOf( snippet );
         var hlText = orig.slice( idx, Math.min( idx + needle.length, orig.length ) );
         var hlText = orig.slice( idx, Math.min( idx + needle.length, orig.length ) );
Line 899: Line 872:
       }
       }


      // Fallback: raw text nodes (before tagTextNodes runs)
       if ( !found ) {
       if ( !found ) {
         var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT );
         var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT );
Line 924: Line 896:
     }
     }


     // Delay to let tagTextNodes() and MW rendering finish
     doHighlight();
    setTimeout( doHighlight, 600 );
   }
   }


Line 951: Line 922:
     wireUllekhaLinks();
     wireUllekhaLinks();
   }
   }
}() );/* ── Search result highlight ──────────────────────────────────── */
( function () {
  function storeQueryForLink( url, query ) {
    try {
      var a = document.createElement( 'a' );
      a.href = url;
      sessionStorage.setItem( 'gr_search_hl', JSON.stringify({
        query:    query,
        pathname: a.pathname
      }) );
    } catch(e) {}
  }
  function applyHighlight() {
    var stored;
    try {
      stored = JSON.parse( sessionStorage.getItem( 'gr_search_hl' ) || 'null' );
    } catch(e) { return; }
    if ( !stored || !stored.query ) return;
    var currentPath = window.location.pathname;
    var storedPath  = stored.pathname || '';
    function normPath(p) { return decodeURIComponent(p).replace(/\/+$/, ''); }
    if ( storedPath && normPath(storedPath) !== normPath(currentPath) ) {
      try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}
      return;
    }
    var query = stored.query.trim();
    if ( !query ) return;
    try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}
    var delays = [0, 200, 600];
    delays.forEach( function(ms) {
      setTimeout( function () {
        if ( !document.querySelector( '.gr-search-hl' ) ) {
          highlightText( query );
        }
      }, ms );
    } );
  }
  function highlightText( query ) {
    var content = document.querySelector( '#mw-content-text .mw-parser-output' );
    if ( !content ) return;
    var raw = query.replace( /^"|"$/g, '' ).trim();
    if ( !raw ) return;
    var patterns = [];
    patterns.push( escapeRegex( raw ) );
    raw.split( /\s+/ ).forEach( function(w) {
      if ( w.length >= 2 ) patterns.push( escapeRegex( w ) );
    } );
    var matched = false;
    for ( var pi = 0; pi < patterns.length; pi++ ) {
      var re;
      try { re = new RegExp( '(' + patterns[pi] + ')', 'gi' ); }
      catch(e) { continue; }
      var count = wrapMatches( content, re );
      if ( count > 0 ) { matched = true; break; }
    }
    if ( !matched ) return;
    var first = document.querySelector( '.gr-search-hl' );
    if ( first ) {
      first.scrollIntoView({ behavior: 'smooth', block: 'center' });
      first.classList.add( 'gr-search-hl-pulse' );
      setTimeout( function() {
        first.classList.remove( 'gr-search-hl-pulse' );
      }, 2000 );
    }
    showDismissBar( query );
  }
  function escapeRegex( s ) {
    return s.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
  }
  function wrapMatches( root, re ) {
    var count = 0;
    var walker = document.createTreeWalker(
      root, NodeFilter.SHOW_TEXT, {
        acceptNode: function( node ) {
          var p = node.parentElement;
          if ( !p ) return NodeFilter.FILTER_REJECT;
          var tag = p.tagName.toUpperCase();
          if ( tag === 'SCRIPT' || tag === 'STYLE' || tag === 'NOSCRIPT' ) return NodeFilter.FILTER_REJECT;
          if ( p.classList.contains( 'gr-search-hl' ) ) return NodeFilter.FILTER_REJECT;
          return NodeFilter.FILTER_ACCEPT;
        }
      }, false
    );
    var nodes = [];
    var node;
    while ( ( node = walker.nextNode() ) ) nodes.push( node );
    nodes.forEach( function( textNode ) {
      var val = textNode.nodeValue;
      if ( !re.test( val ) ) return;
      re.lastIndex = 0;
      var frag = document.createDocumentFragment();
      var last = 0;
      var m;
      while ( ( m = re.exec( val ) ) !== null ) {
        if ( m.index > last ) {
          frag.appendChild( document.createTextNode( val.slice( last, m.index ) ) );
        }
        var span = document.createElement( 'span' );
        span.className = 'gr-search-hl';
        span.textContent = m[0];
        frag.appendChild( span );
        last = m.index + m[0].length;
        count++;
      }
      if ( last < val.length ) {
        frag.appendChild( document.createTextNode( val.slice( last ) ) );
      }
      textNode.parentNode.replaceChild( frag, textNode );
    } );
    return count;
  }
  function showDismissBar( query ) {
    var isMob = window.innerWidth < 768;
    var bar = document.createElement( 'div' );
    bar.id = 'gr-hl-bar';
    if ( isMob ) {
      bar.style.cssText = [
        'position:fixed',
        'top:calc(var(--gr-header-height,56px) + var(--gr-toc-top,52px) + 8px)',
        'left:50%', 'transform:translateX(-50%)',
        'z-index:10200',
        'background:#b5451b', 'color:#fff',
        'padding:8px 14px',
        'border-radius:24px',
        'display:flex', 'align-items:center', 'gap:8px',
        'font-family:system-ui,sans-serif', 'font-size:13px',
        'box-shadow:0 3px 12px rgba(0,0,0,0.25)',
        'white-space:nowrap',
        'max-width:calc(100vw - 32px)'
      ].join(';');
    } else {
      bar.style.cssText = [
        'position:fixed', 'bottom:0', 'left:0', 'right:0', 'z-index:10200',
        'background:#b5451b', 'color:#fff', 'padding:10px 16px',
        'display:flex', 'align-items:center', 'justify-content:space-between',
        'font-family:system-ui,sans-serif', 'font-size:14px',
        'box-shadow:0 -2px 8px rgba(0,0,0,0.2)'
      ].join(';');
    }
    var count = document.querySelectorAll( '.gr-search-hl' ).length;
    if ( isMob ) {
      bar.innerHTML =
        '<span style="flex-shrink:0">🔍 ' + count + '</span>' +
        '<button id="gr-hl-prev" style="background:rgba(255,255,255,0.2);border:none;color:#fff;min-width:36px;height:36px;border-radius:50%;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;">↑</button>' +
        '<button id="gr-hl-next" style="background:rgba(255,255,255,0.2);border:none;color:#fff;min-width:36px;height:36px;border-radius:50%;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;">↓</button>' +
        '<button id="gr-hl-results" style="background:rgba(255,255,255,0.2);border:none;color:#fff;min-width:36px;height:36px;border-radius:50%;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center;">←</button>' +
        '<button id="gr-hl-dismiss" style="background:rgba(255,255,255,0.15);border:none;color:#fff;min-width:36px;height:36px;border-radius:50%;cursor:pointer;font-size:16px;display:flex;align-items:center;justify-content:center;">✕</button>';
    } else {
      var nav = document.createElement( 'div' );
      nav.style.cssText = 'display:flex;align-items:center;gap:12px;';
      nav.innerHTML =
        '<span>🔍 <strong>' + escHtml(query) + '</strong> — ' + count + ' match' + (count===1?'':'es') + '</span>' +
        '<button id="gr-hl-prev" style="background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;">↑ Prev</button>' +
        '<button id="gr-hl-next" style="background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;">↓ Next</button>' +
        '<button id="gr-hl-results" style="background:rgba(255,255,255,0.2);border:none;color:#fff;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;">← Results</button>';
      bar.appendChild( nav );
    }
    var dismiss = isMob
      ? bar.querySelector( '#gr-hl-dismiss' )
      : ( function() {
          var b = document.createElement( 'button' );
          b.textContent = '✕ Close';
          b.id = 'gr-hl-dismiss';
          b.style.cssText = 'background:rgba(255,255,255,0.15);border:none;color:#fff;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:13px;min-height:32px;';
          bar.appendChild( b );
          return b;
        }() );
    dismiss.onclick = function () {
      clearHighlights();
      bar.remove();
    };
    document.body.appendChild( bar );
    var hlEls = Array.from( document.querySelectorAll( '.gr-search-hl' ) );
    var currentIdx = 0;
    function goTo( idx ) {
      hlEls.forEach( function(el) { el.classList.remove( 'gr-search-hl-current' ); } );
      currentIdx = ( idx + hlEls.length ) % hlEls.length;
      var el = hlEls[ currentIdx ];
      el.classList.add( 'gr-search-hl-current' );
      el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
    var nextBtn    = document.getElementById( 'gr-hl-next' );
    var prevBtn    = document.getElementById( 'gr-hl-prev' );
    var resultsBtn = document.getElementById( 'gr-hl-results' );
    if ( nextBtn ) nextBtn.onclick = function() { goTo( currentIdx + 1 ); };
    if ( prevBtn ) prevBtn.onclick = function() { goTo( currentIdx - 1 ); };
    if ( resultsBtn ) resultsBtn.onclick = function() {
      bar.remove();
      /* Re-open search dialog with the same query prefilled */
      if ( window.showSearchDialog ) { window.showSearchDialog( query ); }
    };
  }
  function clearHighlights() {
    document.querySelectorAll( '.gr-search-hl' ).forEach( function( span ) {
      var parent = span.parentNode;
      while ( span.firstChild ) parent.insertBefore( span.firstChild, span );
      parent.removeChild( span );
    } );
  }
  function escHtml( s ) {
    return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  }
  function injectHighlightCSS() {
    if ( document.getElementById( 'gr-hl-css' ) ) return;
    var s = document.createElement( 'style' );
    s.id = 'gr-hl-css';
    s.textContent = [
      '.gr-search-hl{',
      '  background:#fff176;color:#1a1a1a;',
      '  border-radius:2px;padding:0 1px;',
      '  box-shadow:0 0 0 1px rgba(181,69,27,0.25);',
      '}',
      '.gr-search-hl-current{',
      '  background:#ffb300!important;',
      '  box-shadow:0 0 0 2px #b5451b!important;',
      '}',
      '@keyframes gr-hl-pulse{',
      '  0%{background:#ffb300;}',
      '  50%{background:#fff176;}',
      '  100%{background:#fff176;}',
      '}',
      '.gr-search-hl-pulse{animation:gr-hl-pulse 1.2s ease 2;}',
    ].join('');
    document.head.appendChild( s );
  }
  injectHighlightCSS();
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', applyHighlight );
  } else {
    applyHighlight();
  }
  window.grStoreSearchHL = storeQueryForLink;
}() );
/* ═══════════════════════════════════════════════════════════════
  Mobile addon — paste at bottom of MediaWiki:Common.js
  Only runs on Minerva (mobile) skin
  ═══════════════════════════════════════════════════════════════ */
( function () {
  if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
  /* ── 1. CSS injected into <head> ── */
  function injectCSS() {
    if ( document.getElementById( 'gr-mob-css' ) ) return;
    var s = document.createElement( 'style' );
    s.id = 'gr-mob-css';
    s.textContent =
      'body,#mw-mf-viewport,#mw-mf-page-center{padding-top:0!important;margin-top:0!important;}' +
      'html,body,#mw-mf-viewport,#mw-mf-page-center{overflow-x:hidden!important;max-width:100vw!important;}' +
      'header.header-container{background:#b5451b!important;position:sticky!important;top:0!important;z-index:300!important;}' +
      '.minerva-header{background:#b5451b!important;min-height:54px!important;}' +
      '.minerva-header .search-toggle,.minerva-header .minerva-user-notifications{display:none!important;}' +
      '.branding-box a{display:flex!important;align-items:center!important;' +
        'text-decoration:none!important;max-width:calc(100vw - 80px)!important;}' +
      '.branding-box a::before{content:"";display:block;width:30px;height:30px;flex-shrink:0;' +
        'background:url("/favicon.png") center/contain no-repeat;' +
        'margin-right:8px;}' +
      '.branding-box a span{color:#fff!important;font-size:16px!important;font-weight:700!important;' +
        'font-family:system-ui,sans-serif!important;line-height:1.2!important;' +
        'flex:1 1 auto!important;min-width:0!important;}' +
      '.minerva-header svg path,.minerva-header svg rect,.minerva-header svg circle{fill:#fff!important;}' +
      '.minerva-header label{color:#fff!important;}' +
      '.minerva-tabs,.mw-portlet-associated-pages,.page-actions-menu,' +
        '#page-secondary-actions,.last-modified-bar,.minerva-anon-talk-link{display:none!important;}' +
      '#gr-mob-menu-items{display:block!important;}' +
      '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
        '.minerva-footer-logo,#footer-places-about,' +
        '#footer-places-disclaimers,#footer-places-privacy{display:none!important;}' +
      '#gr-static-bar{position:sticky!important;top:54px!important;z-index:200!important;}' +
      '.mf-section-0,.mf-section-1,.mf-section-2,.mf-section-3,.mf-section-4,' +
        '.mf-section-5,.mf-section-6,.mf-section-7,.mf-section-8,.mf-section-9,' +
        '.mf-section-10{display:block!important;visibility:visible!important;}' +
      '.collapsible-block{display:block!important;}' +
      '.section-heading .indicator,.collapsible-heading .indicator{display:none!important;}' +
      '.section-heading,.collapsible-heading{pointer-events:none!important;}' +
      '.gr-home-grid{flex-direction:column!important;flex-wrap:nowrap!important;' +
        'gap:12px!important;width:100%!important;}' +
      '.gr-home-card{width:100%!important;max-width:100%!important;' +
        'min-width:unset!important;box-sizing:border-box!important;flex:none!important;}' +
      '.gr-home-toggle{flex-wrap:wrap!important;}' +
      '.mw-parser-output{font-size:18px!important;line-height:1.8!important;}' +
      '.mw-parser-output h2,.mw-parser-output h3{width:100%!important;}' +
      '.bhashyam-block{margin-left:8px!important;}' +
      '#footer,.mw-footer,.catlinks,#catlinks{display:none!important;}' +
      '.gr-mob-toc-panel{position:fixed!important;top:0!important;left:0!important;' +
        'bottom:0!important;width:82vw!important;max-width:340px!important;' +
        'background:#fff!important;z-index:10400!important;' +
        'box-shadow:4px 0 28px rgba(0,0,0,0.22)!important;overflow-y:auto!important;' +
        'padding:0 0 40px!important;transform:translateX(-110%)!important;' +
        'transition:transform 0.26s cubic-bezier(0.4,0,0.2,1)!important;display:block!important;}' +
      '.gr-mob-toc-panel.open{transform:translateX(0)!important;}' +
      '.gr-mob-toc-backdrop{display:none!important;position:fixed!important;inset:0!important;' +
        'background:rgba(0,0,0,0.4)!important;z-index:10399!important;}' +
      '.gr-mob-toc-backdrop.open{display:block!important;}' +
      '.gr-mob-toc-header{position:sticky!important;top:0!important;background:#fff!important;' +
        'display:flex!important;align-items:center!important;justify-content:space-between!important;' +
        'padding:16px 16px 12px!important;border-bottom:1px solid #f0ebe6!important;z-index:1!important;}' +
      '.gr-mob-toc-title{font-size:13px!important;font-weight:700!important;text-transform:uppercase!important;' +
        'letter-spacing:0.08em!important;color:#b5451b!important;font-family:system-ui,sans-serif!important;}' +
      '.gr-mob-toc-close{background:none!important;border:none!important;' +
        'font-size:22px!important;color:#999!important;cursor:pointer!important;padding:4px 8px!important;}' +
      '.gr-mob-toc-body{padding:12px 16px!important;}' +
      '.gr-mob-toc-body a{display:block!important;font-size:16px!important;line-height:1.6!important;' +
        'color:#2c1810!important;text-decoration:none!important;padding:8px 0!important;' +
        'border-bottom:1px solid #f5f0ed!important;}';
    document.head.appendChild( s );
  }
  /* ── 2. Expand hidden sections ── */
  function expandSections() {
    document.querySelectorAll( '[class*="mf-section-"], .collapsible-block' )
      .forEach( function ( el ) {
        el.removeAttribute( 'hidden' );
        el.style.setProperty( 'display', 'block', 'important' );
        el.style.setProperty( 'visibility', 'visible', 'important' );
        el.removeAttribute( 'aria-hidden' );
      } );
    document.querySelectorAll( '.section-heading, .collapsible-heading' )
      .forEach( function ( el ) {
        el.setAttribute( 'aria-expanded', 'true' );
        el.style.setProperty( 'pointer-events', 'none', 'important' );
      } );
  }
  function watchSections() {
    var t = null;
    var obs = new MutationObserver( function ( ms ) {
      if ( ms.some( function(m){ return m.attributeName === 'hidden'; } ) ) {
        clearTimeout(t); t = setTimeout( expandSections, 30 );
      }
    } );
    var root = document.querySelector( '#mw-content-text' ) || document.body;
    obs.observe( root, { subtree:true, attributes:true, attributeFilter:['hidden','aria-hidden'] } );
  }
  /* ── 3. Body padding observer ── */
  function watchBodyPadding() {
    new MutationObserver( function () {
      if ( document.body.style.paddingTop && document.body.style.paddingTop !== '0px' ) {
        document.body.style.paddingTop = '';
      }
    } ).observe( document.body, { attributes:true, attributeFilter:['style'] } );
  }
  /* ── 4. Inject custom links into drawer ── */
  function injectMenuLinks() {
    if ( document.getElementById( 'gr-mob-menu-items' ) ) return;
    var navDrawer = document.querySelector( '.navigation-drawer' );
    if ( !navDrawer ) return;
    var wrap = document.createElement( 'div' );
    wrap.id = 'gr-mob-menu-items';
    wrap.style.cssText = 'width:100%;background:#fff;margin-top:8px;';
    var itemStyle = 'display:flex;align-items:center;gap:14px;padding:15px 20px;' +
      'font-size:16px;color:#2c1810;text-decoration:none;' +
      'font-family:system-ui,sans-serif;border-bottom:1px solid #f0ebe6;background:#fff;';
    function makeItem( href, emoji, label ) {
      var a = document.createElement( 'a' );
      a.href = href;
      a.style.cssText = itemStyle;
      a.innerHTML =
        '<span>' + label + '</span>';
      return a;
    }
    wrap.appendChild( makeItem( '/Main_Page', '', 'Home' ) );
    wrap.appendChild( makeItem( '/My_wiki:Help', '', 'Help' ) );
    wrap.appendChild( makeItem( '/My_wiki:About', '', 'About' ) );
    var userName = window.mw ? mw.config.get( 'wgUserName' ) : null;
    if ( userName ) {
      var la = document.querySelector( 'a[href*="action=logout"]' );
      wrap.appendChild( makeItem(
        la ? la.href : '/index.php?title=Special:UserLogout', '', 'Log out'
      ) );
    } else {
      wrap.appendChild( makeItem( '/index.php?title=Special:UserLogin', '', 'Log in' ) );
    }
    var pageLeft = document.getElementById( 'mw-mf-page-left' );
    if ( pageLeft ) {
      while ( pageLeft.firstChild ) pageLeft.removeChild( pageLeft.firstChild );
      pageLeft.style.removeProperty( 'display' );
      pageLeft.appendChild( wrap );
    } else {
      navDrawer.appendChild( wrap );
    }
    document.querySelectorAll(
      '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
      '.minerva-footer-logo,#footer-places-about,' +
      '#footer-places-disclaimers,#footer-places-privacy'
    ).forEach( function(el) { el.style.setProperty('display','none','important'); } );
  }
  /* ── 5. Mobile TOC overlay ── */
  var _tocDone = false;
  function initToc() {
    if ( _tocDone ) return;
    var tocList = document.querySelector( '.vector-toc-contents, .vector-toc .vector-toc-list' );
    if ( !tocList ) return;
    if ( !tocList.querySelector( 'li' ) ) return;
    _tocDone = true;
    var bd = document.createElement( 'div' );
    bd.className = 'gr-mob-toc-backdrop';
    document.body.appendChild( bd );
    var panel = document.createElement( 'div' );
    panel.className = 'gr-mob-toc-panel';
    var hdr = document.createElement( 'div' );
    hdr.className = 'gr-mob-toc-header';
    var ttl = document.createElement( 'div' );
    ttl.className = 'gr-mob-toc-title';
    ttl.textContent = 'विषयसूची';
    var cls = document.createElement( 'button' );
    cls.className = 'gr-mob-toc-close';
    cls.textContent = '✕';
    hdr.appendChild( ttl ); hdr.appendChild( cls );
    panel.appendChild( hdr );
    var body = document.createElement( 'div' );
    body.className = 'gr-mob-toc-body';
    body.appendChild( tocList.cloneNode( true ) );
    panel.appendChild( body );
    document.body.appendChild( panel );
    var btn = document.createElement( 'button' );
    btn.id = 'gr-mob-toc-btn';
    btn.innerHTML = '☰ &nbsp;Contents';
    btn.style.cssText = 'position:fixed;bottom:148px;left:16px;z-index:9100;' +
      'background:#fff;border:1.5px solid #b5451b;border-radius:24px;' +
      'padding:10px 16px;font-size:15px;font-family:system-ui,sans-serif;' +
      'color:#b5451b;font-weight:600;box-shadow:0 3px 14px rgba(0,0,0,0.15);cursor:pointer;';
    document.body.appendChild( btn );
    function open()  { panel.classList.add('open'); bd.classList.add('open'); document.body.style.overflow='hidden'; }
    function close() { panel.classList.remove('open'); bd.classList.remove('open'); document.body.style.overflow=''; }
    btn.onclick = open; cls.onclick = close; bd.onclick = close;
    body.querySelectorAll('a').forEach(function(a){ a.onclick = close; });
  }
  /* ── 6. Moolam / Ullekha links below page title (mobile only) ── */
  function injectMoolaUllekhaLinks() {
    if ( document.getElementById( 'gr-mob-doc-nav' ) ) return;
    var pageName = ( window.mw && mw.config && mw.config.get( 'wgPageName' ) ) || '';
    if ( pageName === 'Main_Page' || !pageName ) return;
    function wikiUrl( slug ) {
      if ( window.mw && mw.util && mw.util.getUrl ) return mw.util.getUrl( slug );
      var ap = ( window.mw && mw.config && mw.config.get( 'wgArticlePath' ) ) || '/wiki/$1';
      return ap.replace( '$1', encodeURIComponent( slug ).replace( /%2F/g, '/' ) );
    }
    var teekaPage      = document.querySelector( '.gr-teeka-page' );
    var primarySlug    = teekaPage
      ? ( teekaPage.getAttribute( 'data-primary' ) || pageName.split('/')[0] )
      : pageName.split('/')[0];
    var docTitleEl    = document.querySelector( '.gr-doc-title' );
    var hasMoolaPage  = docTitleEl && docTitleEl.getAttribute( 'data-has-moola' )  === '1';
    var hasUllekhaPage = docTitleEl && docTitleEl.getAttribute( 'data-has-ullekha' ) === '1';
    var showMoolam  = !!teekaPage || hasMoolaPage;
    var showUllekha = hasUllekhaPage || !!teekaPage;
    if ( !showMoolam && !showUllekha ) return;
    var nav = document.createElement( 'div' );
    nav.id = 'gr-mob-doc-nav';
    nav.style.cssText = 'display:flex;gap:10px;padding:10px 16px 8px;' +
      'background:#fdf8f5;border-bottom:1px solid #f0e0d6;font-family:system-ui,sans-serif;';
    function makeLink( href, label ) {
      var a = document.createElement( 'a' );
      a.href = href; a.textContent = label;
      a.style.cssText = 'display:inline-flex;align-items:center;padding:5px 16px;' +
        'border-radius:20px;background:#fff;border:1.5px solid #e8cfc4;' +
        'color:#b5451b;font-size:14px;font-weight:600;text-decoration:none;';
      return a;
    }
    if ( teekaPage )        nav.appendChild( makeLink( wikiUrl( primarySlug ),              'मूल' ) );
    else if ( hasMoolaPage ) nav.appendChild( makeLink( wikiUrl( primarySlug + '/Moola' ),  'मूलम्' ) );
    if ( showUllekha )      nav.appendChild( makeLink( wikiUrl( primarySlug + '/Ullekha' ), 'उल्लेख' ) );
    /* Insert below the page h1 */
    var h1 = document.getElementById( 'firstHeading' ) ||
            document.querySelector( '.page-heading, h1.firstHeading, .mw-first-heading' );
    if ( h1 && h1.parentNode ) {
      h1.parentNode.insertBefore( nav, h1.nextSibling );
    } else {
      var ct = document.getElementById( 'mw-content-text' );
      if ( ct ) ct.insertBefore( nav, ct.firstChild );
    }
  }
  /* ── Boot ── */
  injectCSS();
  watchBodyPadding();
  function boot() {
    expandSections();
    watchSections();
    injectMenuLinks();
    injectMoolaUllekhaLinks();
    [100, 400, 900, 1800].forEach(function(ms){ setTimeout(expandSections, ms); });
    setTimeout(initToc, 700);
  }
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', boot );
  } else {
    boot();
  }
  if ( window.mw ) {
    mw.hook( 'wikipage.content' ).add(function() {
      setTimeout(function(){
        expandSections(); injectMenuLinks(); injectMoolaUllekhaLinks(); initToc();
      }, 300);
    });
  }
}() );
/**
* grantha-mobile-fixes.js
*/
( function () {
  'use strict';
  mw.hook( 'wikipage.content' ).add( function () {
    setTimeout( function () {
      var blocks = document.querySelectorAll( '.collapsible-block, .toggle-list' );
      Array.prototype.forEach.call( blocks, function ( el ) {
        if ( !el.parentNode ) {
          try { el.remove(); } catch(e) {}
        }
      } );
    }, 0 );
  } );
  if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return;
  mw.loader.using( 'mediawiki.util' ).done( function () {
    $( function () {
      applyHomeToggleOffset();
      window.addEventListener( 'resize', applyHomeToggleOffset, { passive: true } );
      setTimeout( applyHomeToggleOffset, 300 );
      setTimeout( applyHomeToggleOffset, 800 );
    } );
  } );
  function applyHomeToggleOffset() {
    var bar = document.getElementById( 'gr-static-bar' );
    if ( !bar ) return;
    var barRect = bar.getBoundingClientRect();
    var barBottom = Math.round( barRect.bottom );
    var homeEl = document.getElementById( 'gr-home' );
    var toggleEl = document.getElementById( 'gr-home-toggle' );
    var firstChild = document.querySelector(
      '#mw-content-text .mw-parser-output > .gr-home, ' +
      '#mw-content-text .mw-parser-output > *:first-child'
    );
    [ homeEl, toggleEl, firstChild ].forEach( function ( el ) {
      if ( el ) el.style.scrollMarginTop = ( barBottom + 4 ) + 'px';
    } );
    var contentText = document.getElementById( 'mw-content-text' );
    if ( contentText ) {
      var isMob = window.innerWidth < 768 || !!document.getElementById( 'mw-mf-viewport' );
      if ( isMob ) {
        if ( toggleEl ) {
          var toggleRect = toggleEl.getBoundingClientRect();
          if ( toggleRect.top < barBottom ) {
            var currentPT = parseInt( window.getComputedStyle( document.body ).paddingTop, 10 ) || 0;
            var needed    = currentPT + ( barBottom - toggleRect.top ) + 4;
            document.body.style.paddingTop = needed + 'px';
          }
        }
      }
    }
  }
  $( function () {
    var $toggle  = $( '#gr-home-toggle' );
    var $viewG    = $( '#gr-view-grantha' );
    var $viewA    = $( '#gr-view-author' );
    var $btnG    = $( '#gr-toggle-grantha' );
    var $btnA    = $( '#gr-toggle-author' );
    if ( !$toggle.length || !$viewG.length || !$viewA.length ) return;
    if ( $toggle.data( 'gr-wired' ) ) return;
    $toggle.data( 'gr-wired', true );
    function showView( which ) {
      if ( which === 'grantha' ) {
        $viewG.show(); $viewA.hide();
        $btnG.addClass( 'gr-toggle-active' );
        $btnA.removeClass( 'gr-toggle-active' );
      } else {
        $viewA.show(); $viewG.hide();
        $btnA.addClass( 'gr-toggle-active' );
        $btnG.removeClass( 'gr-toggle-active' );
      }
      try { localStorage.setItem( 'grantha_home_tab', which ); } catch(e){}
    }
    $btnG.on( 'click keydown', function(e){
      if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return;
      showView( 'grantha' );
    });
    $btnA.on( 'click keydown', function(e){
      if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return;
      showView( 'author' );
    });
    try {
      var saved = localStorage.getItem( 'grantha_home_tab' );
      if ( saved === 'author' ) showView( 'author' );
      else showView( 'grantha' );
    } catch(e) { showView( 'grantha' ); }
  } );
}() );
}() );