Jump to content

MediaWiki:Common.js: Difference between revisions

From Anandamakaranda
Undo revision 5755 by Chandrashekars (talk)
Tag: Undo
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
     setTimeout( doHighlight, 600 );
     setTimeout( doHighlight, 600 );
   }
   }
Line 951: Line 922:
     wireUllekhaLinks();
     wireUllekhaLinks();
   }
   }
}() );/* ── Search result highlight — highlight query term on arrival ──
}() );/* ── Search result highlight ──────────────────────────────────── */
* When user clicks a search result, we pass the query in the URL
* hash as #gr-search:QUERY or read it from sessionStorage.
* On page load we find all matching text nodes and wrap them.
* ─────────────────────────────────────────────────────────────── */
( function () {
( function () {


  /* ── Step 1: When leaving via a search result link,
    store the query in sessionStorage ── */
   function storeQueryForLink( url, query ) {
   function storeQueryForLink( url, query ) {
     try {
     try {
      /* Store just the pathname so protocol/host differences don't break matching */
       var a = document.createElement( 'a' );
       var a = document.createElement( 'a' );
       a.href = url;
       a.href = url;
       sessionStorage.setItem( 'gr_search_hl', JSON.stringify({
       sessionStorage.setItem( 'gr_search_hl', JSON.stringify({
         query:    query,
         query:    query,
         pathname: a.pathname   // e.g. "/Brahmasutra"
         pathname: a.pathname
       }) );
       }) );
     } catch(e) {}
     } catch(e) {}
   }
   }


  /* ── Step 2: On page load, check if we arrived from a search result ── */
   function applyHighlight() {
   function applyHighlight() {
     var stored;
     var stored;
Line 980: Line 943:
     if ( !stored || !stored.query ) return;
     if ( !stored || !stored.query ) return;


    /* Check pathname matches — lenient comparison */
     var currentPath = window.location.pathname;
     var currentPath = window.location.pathname;
     var storedPath  = stored.pathname || '';
     var storedPath  = stored.pathname || '';
    /* Normalize: remove trailing slash, decode */
     function normPath(p) { return decodeURIComponent(p).replace(/\/+$/, ''); }
     function normPath(p) { return decodeURIComponent(p).replace(/\/+$/, ''); }
     if ( storedPath && normPath(storedPath) !== normPath(currentPath) ) {
     if ( storedPath && normPath(storedPath) !== normPath(currentPath) ) {
      /* Different page — clear and bail */
       try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}
       try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}
       return;
       return;
Line 994: Line 954:
     if ( !query ) return;
     if ( !query ) return;


    /* Clear so refreshing doesn't re-highlight */
     try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}
     try { sessionStorage.removeItem( 'gr_search_hl' ); } catch(e) {}


    /* Wait for content + Common.js tagTextNodes to finish rendering */
     var delays = [400, 900, 1500];
     var delays = [400, 900, 1500];
     delays.forEach( function(ms) {
     delays.forEach( function(ms) {
       setTimeout( function () {
       setTimeout( function () {
        /* Only run if not already highlighted */
         if ( !document.querySelector( '.gr-search-hl' ) ) {
         if ( !document.querySelector( '.gr-search-hl' ) ) {
           highlightText( query );
           highlightText( query );
Line 1,009: Line 966:
   }
   }


  /* ── Core: walk text nodes and wrap matches ── */
   function highlightText( query ) {
   function highlightText( query ) {
     var content = document.querySelector( '#mw-content-text .mw-parser-output' );
     var content = document.querySelector( '#mw-content-text .mw-parser-output' );
     if ( !content ) return;
     if ( !content ) return;


    /* Normalise query — strip quotes, split on spaces */
     var raw = query.replace( /^"|"$/g, '' ).trim();
     var raw = query.replace( /^"|"$/g, '' ).trim();
     if ( !raw ) return;
     if ( !raw ) return;


    /* Build regex — escape special chars, match whole query first,
      fall back to individual words */
     var patterns = [];
     var patterns = [];
    /* Full phrase */
     patterns.push( escapeRegex( raw ) );
     patterns.push( escapeRegex( raw ) );
    /* Individual words (min 2 chars) */
     raw.split( /\s+/ ).forEach( function(w) {
     raw.split( /\s+/ ).forEach( function(w) {
       if ( w.length >= 2 ) patterns.push( escapeRegex( w ) );
       if ( w.length >= 2 ) patterns.push( escapeRegex( w ) );
Line 1,040: Line 991:
     if ( !matched ) return;
     if ( !matched ) return;


    /* Scroll to first highlight */
     var first = document.querySelector( '.gr-search-hl' );
     var first = document.querySelector( '.gr-search-hl' );
     if ( first ) {
     if ( first ) {
       first.scrollIntoView({ behavior: 'smooth', block: 'center' });
       first.scrollIntoView({ behavior: 'smooth', block: 'center' });
      /* Pulse animation */
       first.classList.add( 'gr-search-hl-pulse' );
       first.classList.add( 'gr-search-hl-pulse' );
       setTimeout( function() {
       setTimeout( function() {
Line 1,051: Line 1,000:
     }
     }


    /* Show dismiss button */
     showDismissBar( query );
     showDismissBar( query );
   }
   }
Line 1,064: Line 1,012:
       root, NodeFilter.SHOW_TEXT, {
       root, NodeFilter.SHOW_TEXT, {
         acceptNode: function( node ) {
         acceptNode: function( node ) {
          /* Skip inside script, style, our own highlights */
           var p = node.parentElement;
           var p = node.parentElement;
           if ( !p ) return NodeFilter.FILTER_REJECT;
           if ( !p ) return NodeFilter.FILTER_REJECT;
Line 1,107: Line 1,054:
   }
   }


  /* ── Dismiss bar ── */
   function showDismissBar( query ) {
   function showDismissBar( query ) {
     var isMob = window.innerWidth < 768;
     var isMob = window.innerWidth < 768;
Line 1,113: Line 1,059:
     bar.id = 'gr-hl-bar';
     bar.id = 'gr-hl-bar';


    /* On mobile: float as a pill near top to avoid bottom bar conflicts.
      On desktop: sit at bottom. */
     if ( isMob ) {
     if ( isMob ) {
       bar.style.cssText = [
       bar.style.cssText = [
Line 1,143: Line 1,087:


     if ( isMob ) {
     if ( isMob ) {
      /* Mobile: compact pill — just count + prev/next + dismiss */
       bar.innerHTML =
       bar.innerHTML =
         '<span style="flex-shrink:0">🔍 ' + count + '</span>' +
         '<span style="flex-shrink:0">🔍 ' + count + '</span>' +
Line 1,150: Line 1,093:
         '<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>';
         '<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 {
     } else {
      /* Desktop: full bar */
       var nav = document.createElement( 'div' );
       var nav = document.createElement( 'div' );
       nav.style.cssText = 'display:flex;align-items:center;gap:12px;';
       nav.style.cssText = 'display:flex;align-items:center;gap:12px;';
Line 1,178: Line 1,120:
     document.body.appendChild( bar );
     document.body.appendChild( bar );


    /* Prev / Next navigation */
     var hlEls = Array.from( document.querySelectorAll( '.gr-search-hl' ) );
     var hlEls = Array.from( document.querySelectorAll( '.gr-search-hl' ) );
     var currentIdx = 0;
     var currentIdx = 0;
Line 1,207: Line 1,148:
   }
   }


  /* ── Inject CSS for highlights ── */
   function injectHighlightCSS() {
   function injectHighlightCSS() {
     if ( document.getElementById( 'gr-hl-css' ) ) return;
     if ( document.getElementById( 'gr-hl-css' ) ) return;
Line 1,232: Line 1,172:
   }
   }


  /* ── Boot ── */
   injectHighlightCSS();
   injectHighlightCSS();
   if ( document.readyState === 'loading' ) {
   if ( document.readyState === 'loading' ) {
Line 1,240: Line 1,179:
   }
   }


  /* ── Expose storeQueryForLink for readerToolbar to call ── */
   window.grStoreSearchHL = storeQueryForLink;
   window.grStoreSearchHL = storeQueryForLink;


Line 1,251: Line 1,189:
   if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
   if ( !document.body.classList.contains( 'skin-minerva' ) ) return;


   /* ── 1. CSS injected into <head> — beats all other stylesheets ── */
   /* ── 1. CSS injected into <head> ── */
   function injectCSS() {
   function injectCSS() {
     if ( document.getElementById( 'gr-mob-css' ) ) return;
     if ( document.getElementById( 'gr-mob-css' ) ) return;
Line 1,257: Line 1,195:
     s.id = 'gr-mob-css';
     s.id = 'gr-mob-css';
     s.textContent =
     s.textContent =
      /* Body padding — readerToolbar sets this, we clear it */
       'body,#mw-mf-viewport,#mw-mf-page-center{padding-top:0!important;margin-top:0!important;}' +
       '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;}' +
       'html,body,#mw-mf-viewport,#mw-mf-page-center{overflow-x:hidden!important;max-width:100vw!important;}' +
      /* Header orange + sticky */
       'header.header-container{background:#b5451b!important;position:sticky!important;top:0!important;z-index:300!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{background:#b5451b!important;min-height:54px!important;}' +
      /* Header: keep only hamburger + branding, hide search */
       '.minerva-header .search-toggle,.minerva-header .minerva-user-notifications{display:none!important;}' +
       '.minerva-header .search-toggle,.minerva-header .minerva-user-notifications{display:none!important;}' +
      /* Logo row: icon + title wrap cleanly */
       '.branding-box a{display:flex!important;align-items:center!important;' +
       '.branding-box a{display:flex!important;align-items:center!important;' +
         'text-decoration:none!important;max-width:calc(100vw - 80px)!important;}' +
         'text-decoration:none!important;max-width:calc(100vw - 80px)!important;}' +
Line 1,277: Line 1,208:
         'font-family:system-ui,sans-serif!important;line-height:1.2!important;' +
         'font-family:system-ui,sans-serif!important;line-height:1.2!important;' +
         'flex:1 1 auto!important;min-width:0!important;}' +
         'flex:1 1 auto!important;min-width:0!important;}' +
      /* Header icons white */
       '.minerva-header svg path,.minerva-header svg rect,.minerva-header svg circle{fill:#fff!important;}' +
       '.minerva-header svg path,.minerva-header svg rect,.minerva-header svg circle{fill:#fff!important;}' +
       '.minerva-header label{color:#fff!important;}' +
       '.minerva-header label{color:#fff!important;}' +
      /* Hide Page/Discussion tabs + icon toolbar */
       '.minerva-tabs,.mw-portlet-associated-pages,.page-actions-menu,' +
       '.minerva-tabs,.mw-portlet-associated-pages,.page-actions-menu,' +
         '#page-secondary-actions,.last-modified-bar,.minerva-anon-talk-link{display:none!important;}' +
         '#page-secondary-actions,.last-modified-bar,.minerva-anon-talk-link{display:none!important;}' +
      /* Drawer: hide default items, show ours */
      /* #mw-mf-page-left is kept — we replace its content in JS */ '' +
       '#gr-mob-menu-items{display:block!important;}' +
       '#gr-mob-menu-items{display:block!important;}' +
      /* Drawer footer: hide About/Disclaimers */
       '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
       '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
         '.minerva-footer-logo,#footer-places-about,' +
         '.minerva-footer-logo,#footer-places-about,' +
         '#footer-places-disclaimers,#footer-places-privacy{display:none!important;}' +
         '#footer-places-disclaimers,#footer-places-privacy{display:none!important;}' +
      /* ReaderToolbar below header */
       '#gr-static-bar{position:sticky!important;top:54px!important;z-index:200!important;}' +
       '#gr-static-bar{position:sticky!important;top:54px!important;z-index:200!important;}' +
      /* Sections expanded */
       '.mf-section-0,.mf-section-1,.mf-section-2,.mf-section-3,.mf-section-4,' +
       '.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-5,.mf-section-6,.mf-section-7,.mf-section-8,.mf-section-9,' +
Line 1,305: Line 1,223:
       '.section-heading .indicator,.collapsible-heading .indicator{display:none!important;}' +
       '.section-heading .indicator,.collapsible-heading .indicator{display:none!important;}' +
       '.section-heading,.collapsible-heading{pointer-events:none!important;}' +
       '.section-heading,.collapsible-heading{pointer-events:none!important;}' +
      /* Home grid single column */
       '.gr-home-grid{flex-direction:column!important;flex-wrap:nowrap!important;' +
       '.gr-home-grid{flex-direction:column!important;flex-wrap:nowrap!important;' +
         'gap:12px!important;width:100%!important;}' +
         'gap:12px!important;width:100%!important;}' +
Line 1,312: Line 1,228:
         'min-width:unset!important;box-sizing:border-box!important;flex:none!important;}' +
         'min-width:unset!important;box-sizing:border-box!important;flex:none!important;}' +
       '.gr-home-toggle{flex-wrap:wrap!important;}' +
       '.gr-home-toggle{flex-wrap:wrap!important;}' +
      /* Content */
       '.mw-parser-output{font-size:18px!important;line-height:1.8!important;}' +
       '.mw-parser-output{font-size:18px!important;line-height:1.8!important;}' +
       '.mw-parser-output h2,.mw-parser-output h3{width:100%!important;}' +
       '.mw-parser-output h2,.mw-parser-output h3{width:100%!important;}' +
       '.bhashyam-block{margin-left:8px!important;}' +
       '.bhashyam-block{margin-left:8px!important;}' +
       '#footer,.mw-footer,.catlinks,#catlinks{display:none!important;}' +
       '#footer,.mw-footer,.catlinks,#catlinks{display:none!important;}' +
      /* TOC overlay */
       '.gr-mob-toc-panel{position:fixed!important;top:0!important;left:0!important;' +
       '.gr-mob-toc-panel{position:fixed!important;top:0!important;left:0!important;' +
         'bottom:0!important;width:82vw!important;max-width:340px!important;' +
         'bottom:0!important;width:82vw!important;max-width:340px!important;' +
Line 1,360: Line 1,272:
   }
   }


  /* Persistent observer — fight MF re-collapsing */
   function watchSections() {
   function watchSections() {
     var t = null;
     var t = null;
Line 1,383: Line 1,294:
   /* ── 4. Inject custom links into drawer ── */
   /* ── 4. Inject custom links into drawer ── */
   function injectMenuLinks() {
   function injectMenuLinks() {
    /* Already injected */
     if ( document.getElementById( 'gr-mob-menu-items' ) ) return;
     if ( document.getElementById( 'gr-mob-menu-items' ) ) return;


    /* The drawer container is .navigation-drawer (the <nav> element)
      Our links go AFTER the #mw-mf-page-left div (which we hide via CSS) */
     var navDrawer = document.querySelector( '.navigation-drawer' );
     var navDrawer = document.querySelector( '.navigation-drawer' );
     if ( !navDrawer ) return;
     if ( !navDrawer ) return;
Line 1,422: Line 1,330:
     }
     }


    /* Replace contents of #mw-mf-page-left — it's inside the
      toggle-list mechanism so it only shows when drawer is open */
     var pageLeft = document.getElementById( 'mw-mf-page-left' );
     var pageLeft = document.getElementById( 'mw-mf-page-left' );
     if ( pageLeft ) {
     if ( pageLeft ) {
      /* Clear default items and insert ours */
       while ( pageLeft.firstChild ) pageLeft.removeChild( pageLeft.firstChild );
       while ( pageLeft.firstChild ) pageLeft.removeChild( pageLeft.firstChild );
       pageLeft.style.removeProperty( 'display' ); /* un-hide it */
       pageLeft.style.removeProperty( 'display' );
       pageLeft.appendChild( wrap );
       pageLeft.appendChild( wrap );
     } else {
     } else {
      /* Fallback */
       navDrawer.appendChild( wrap );
       navDrawer.appendChild( wrap );
     }
     }


    /* Hide footer portlets */
     document.querySelectorAll(
     document.querySelectorAll(
       '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
       '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
Line 1,490: Line 1,393:
     btn.onclick = open; cls.onclick = close; bd.onclick = close;
     btn.onclick = open; cls.onclick = close; bd.onclick = close;
     body.querySelectorAll('a').forEach(function(a){ a.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 );
    }
   }
   }


Line 1,500: Line 1,456:
     watchSections();
     watchSections();
     injectMenuLinks();
     injectMenuLinks();
    injectMoolaUllekhaLinks();
     [100, 400, 900, 1800].forEach(function(ms){ setTimeout(expandSections, ms); });
     [100, 400, 900, 1800].forEach(function(ms){ setTimeout(expandSections, ms); });
     setTimeout(initToc, 700);
     setTimeout(initToc, 700);
Line 1,512: Line 1,469:
   if ( window.mw ) {
   if ( window.mw ) {
     mw.hook( 'wikipage.content' ).add(function() {
     mw.hook( 'wikipage.content' ).add(function() {
       setTimeout(function(){ expandSections(); injectMenuLinks(); initToc(); }, 300);
       setTimeout(function(){
        expandSections(); injectMenuLinks(); injectMoolaUllekhaLinks(); initToc();
      }, 300);
     });
     });
   }
   }
Line 1,519: Line 1,478:
/**
/**
  * grantha-mobile-fixes.js
  * grantha-mobile-fixes.js
* Add this to MediaWiki:Common.js (or load as a separate gadget)
*
* Fixes:
*  1. Patches Minerva's ToggleList to guard against null parentNode
*    (prevents "Cannot read properties of null" crash on Main_Page)
*  2. Keeps #gr-home-toggle visible below #gr-static-bar on mobile
*  3. Wires the grantha/author tab toggle if it hasn't been wired yet
  */
  */


Line 1,531: Line 1,483:
   'use strict';
   'use strict';


  /* ══════════════════════════════════════════════════════════════
  * 1. TOGGLELIST NULL GUARD
  * Minerva's initMobile.js runs ToggleList.init() which calls
  * el.parentNode on every .collapsible-block it finds.
  * On Main_Page, some of these are dynamically injected and may
  * not yet be in the live DOM when ToggleList runs.
  * We patch by removing any element that has no parentNode
  * before ToggleList.init() fires.
  * ══════════════════════════════════════════════════════════════ */
   mw.hook( 'wikipage.content' ).add( function () {
   mw.hook( 'wikipage.content' ).add( function () {
    /* Wait one tick so Minerva's own DOMReady handlers run first */
     setTimeout( function () {
     setTimeout( function () {
       var blocks = document.querySelectorAll( '.collapsible-block, .toggle-list' );
       var blocks = document.querySelectorAll( '.collapsible-block, .toggle-list' );
       Array.prototype.forEach.call( blocks, function ( el ) {
       Array.prototype.forEach.call( blocks, function ( el ) {
        /* If the element has no parentNode it's detached — remove it
          before ToggleList tries to call parentNode on it          */
         if ( !el.parentNode ) {
         if ( !el.parentNode ) {
           try { el.remove(); } catch(e) {}
           try { el.remove(); } catch(e) {}
Line 1,553: Line 1,493:
     }, 0 );
     }, 0 );
   } );
   } );
  /* ══════════════════════════════════════════════════════════════
  * 2. MAIN PAGE: keep #gr-home-toggle below the reader bar
  * ══════════════════════════════════════════════════════════════ */


   if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return;
   if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return;
Line 1,563: Line 1,499:
     $( function () {
     $( function () {
       applyHomeToggleOffset();
       applyHomeToggleOffset();
      /* Re-apply when bar height might change (orientation change, etc.) */
       window.addEventListener( 'resize', applyHomeToggleOffset, { passive: true } );
       window.addEventListener( 'resize', applyHomeToggleOffset, { passive: true } );
      /* Re-apply after a short delay in case the bar hasn't rendered yet */
       setTimeout( applyHomeToggleOffset, 300 );
       setTimeout( applyHomeToggleOffset, 300 );
       setTimeout( applyHomeToggleOffset, 800 );
       setTimeout( applyHomeToggleOffset, 800 );
Line 1,578: Line 1,510:


     var barRect = bar.getBoundingClientRect();
     var barRect = bar.getBoundingClientRect();
    /* barRect.bottom = distance from viewport top to bar bottom.
      On load (before any scroll) this equals the bar's actual top offset
      plus its height. After scrolling (Minerva header gone) it's just
      the bar height since bar.top → 0.                                  */
     var barBottom = Math.round( barRect.bottom );
     var barBottom = Math.round( barRect.bottom );


    /* #gr-home is the outermost container */
     var homeEl = document.getElementById( 'gr-home' );
     var homeEl = document.getElementById( 'gr-home' );
    /* #gr-home-toggle is the tab switcher row */
     var toggleEl = document.getElementById( 'gr-home-toggle' );
     var toggleEl = document.getElementById( 'gr-home-toggle' );
    /* Also target the mw-parser-output first child as a fallback */
     var firstChild = document.querySelector(
     var firstChild = document.querySelector(
       '#mw-content-text .mw-parser-output > .gr-home, ' +
       '#mw-content-text .mw-parser-output > .gr-home, ' +
Line 1,594: Line 1,519:
     );
     );


    /* scroll-margin-top: so that if someone scrolls-to-top the toggle
      is visible below the bar (not behind it)                        */
     [ homeEl, toggleEl, firstChild ].forEach( function ( el ) {
     [ homeEl, toggleEl, firstChild ].forEach( function ( el ) {
       if ( el ) el.style.scrollMarginTop = ( barBottom + 4 ) + 'px';
       if ( el ) el.style.scrollMarginTop = ( barBottom + 4 ) + 'px';
     } );
     } );


    /* The body already has padding-top set by readerToolbar.js.
      But on Main_Page the toggle might STILL be hidden if the
      Minerva header is taller. Force the content area's top
      padding to be at least barBottom.                                */
     var contentText = document.getElementById( 'mw-content-text' );
     var contentText = document.getElementById( 'mw-content-text' );
     if ( contentText ) {
     if ( contentText ) {
      /* Only add extra padding if we're on mobile */
       var isMob = window.innerWidth < 768 || !!document.getElementById( 'mw-mf-viewport' );
       var isMob = window.innerWidth < 768 || !!document.getElementById( 'mw-mf-viewport' );
       if ( isMob ) {
       if ( isMob ) {
        /* Check if the toggle is actually obscured */
         if ( toggleEl ) {
         if ( toggleEl ) {
           var toggleRect = toggleEl.getBoundingClientRect();
           var toggleRect = toggleEl.getBoundingClientRect();
          /* If toggle top < barBottom, the bar is covering it */
           if ( toggleRect.top < barBottom ) {
           if ( toggleRect.top < barBottom ) {
             var currentPT = parseInt( window.getComputedStyle( document.body ).paddingTop, 10 ) || 0;
             var currentPT = parseInt( window.getComputedStyle( document.body ).paddingTop, 10 ) || 0;
Line 1,623: Line 1,539:
   }
   }


  /* ══════════════════════════════════════════════════════════════
  * 3. WIRE THE GRANTHA / AUTHOR TAB TOGGLE
  * In case the toggle script hasn't loaded yet or failed
  * ══════════════════════════════════════════════════════════════ */
   $( function () {
   $( function () {
     var $toggle  = $( '#gr-home-toggle' );
     var $toggle  = $( '#gr-home-toggle' );
Line 1,635: Line 1,547:


     if ( !$toggle.length || !$viewG.length || !$viewA.length ) return;
     if ( !$toggle.length || !$viewG.length || !$viewA.length ) return;
    /* Only wire if not already wired */
     if ( $toggle.data( 'gr-wired' ) ) return;
     if ( $toggle.data( 'gr-wired' ) ) return;
     $toggle.data( 'gr-wired', true );
     $toggle.data( 'gr-wired', true );
Line 1,661: Line 1,572:
     });
     });


    /* Restore last-used tab */
     try {
     try {
       var saved = localStorage.getItem( 'grantha_home_tab' );
       var saved = localStorage.getItem( 'grantha_home_tab' );

Revision as of 18:24, 23 May 2026

/* MediaWiki:Common.js — grantha.io  (v7)
 *
 * Changes vs v6:
 *  1. BUG FIX: Main IIFE was never properly closed — About link block was
 *     pasted inside it without the closing }() ); which broke the module
 *     boundary and killed the by-Author toggle. Each block is now its
 *     own self-contained IIFE.
 *  2. TOC: "Beginning" link removed.
 *  3. TOC: Heading label → "विषयसूची".
 *  4. TOC: मूल / उल्लेख nav links injected at top of TOC panel.
 *  5. TOC: All subsections expanded by default.
 *  6. TOC: Only the active item is bold; all others are normal weight.
 *  7. About link URL derived from wgArticlePath — works under any
 *     sub-path install (e.g. /My_wiki/). Points to "My_wiki:About".
 *  8. TOC customisations skipped on Main_Page and About pages.
 *  9. Uses Vector's native TOC with IntersectionObserver-based active highlight.
 */

( function () {

  var LS_SCRIPT_KEY = 'grantha_reader_script';
  var currentScript = 'deva';

  // ── IAST transliteration ────────────────────────────────────────
  function devanagariToIAST( text ) {
    var CONSONANTS = {
      'क':'k','ख':'kh','ग':'g','घ':'gh','ङ':'ṅ',
      'च':'c','छ':'ch','ज':'j','झ':'jh','ञ':'ñ',
      'ट':'ṭ','ठ':'ṭh','ड':'ḍ','ढ':'ḍh','ण':'ṇ',
      'त':'t','थ':'th','द':'d','ध':'dh','न':'n',
      'प':'p','फ':'ph','ब':'b','भ':'bh','म':'m',
      'य':'y','र':'r','ल':'l','ळ':'ḷ','व':'v',
      'श':'ś','ष':'ṣ','स':'s','ह':'h'
    };
    var DIACRITICS = {
      'ा':'ā','ि':'i','ी':'ī','ु':'u','ू':'ū',
      'ृ':'ṛ','ॄ':'ṝ','े':'e','ै':'ai','ो':'o','ौ':'au'
    };
    var VOWELS = {
      'अ':'a','आ':'ā','इ':'i','ई':'ī','उ':'u','ऊ':'ū',
      'ऋ':'ṛ','ॠ':'ṝ','ए':'e','ऐ':'ai','ओ':'o','औ':'au','ऽ':"'"
    };
    var MISC = {
      'ं':'ṃ','ः':'ḥ','ँ':'m̐','ॐ':'oṃ',
      '०':'0','१':'1','२':'2','३':'3','४':'4',
      '५':'5','६':'6','७':'7','८':'8','९':'9'
    };
    var HALANTA = '्';
    var chars = Array.from( text );
    var result = '';
    var i = 0;
    while ( i < chars.length ) {
      var ch   = chars[ i ];
      var next = chars[ i + 1 ];
      if ( CONSONANTS[ ch ] ) {
        var base = CONSONANTS[ ch ];
        if ( next === HALANTA )          { result += base;               i += 2; }
        else if ( DIACRITICS[ next ] )   { result += base + DIACRITICS[ next ]; i += 2; }
        else if ( next === 'ं' || next === 'ः' ) { result += base + 'a' + MISC[ next ]; i += 2; }
        else                             { result += base + 'a';         i++;    }
      } else if ( VOWELS[ ch ] )         { result += VOWELS[ ch ];       i++; }
      else if ( DIACRITICS[ ch ] )       { result += DIACRITICS[ ch ];   i++; }
      else if ( MISC[ ch ] )             { result += MISC[ ch ];         i++; }
      else                               { result += ch;                 i++; }
    }
    return result;
  }

  // ── Character maps for Kannada / Tamil ─────────────────────────
  var SCRIPT_MAP = {
    kn: {
      'अ':'ಅ','आ':'ಆ','इ':'ಇ','ई':'ಈ','उ':'ಉ','ऊ':'ಊ','ऋ':'ಋ',
      'ए':'ಏ','ऐ':'ಐ','ओ':'ಓ','औ':'ಔ','ऽ':'ಽ',
      'क':'ಕ','ख':'ಖ','ग':'ಗ','घ':'ಘ','ङ':'ಙ',
      'च':'ಚ','छ':'ಛ','ज':'ಜ','झ':'ಝ','ञ':'ಞ',
      'ट':'ಟ','ठ':'ಠ','ड':'ಡ','ढ':'ಢ','ण':'ಣ',
      'त':'ತ','थ':'ಥ','द':'ದ','ध':'ಧ','न':'ನ',
      'प':'ಪ','फ':'ಫ','ब':'ಬ','भ':'ಭ','म':'ಮ',
      'य':'ಯ','र':'ರ','ल':'ಲ','व':'ವ',
      'श':'ಶ','ष':'ಷ','स':'ಸ','ह':'ಹ',
      'ा':'ಾ','ि':'ಿ','ी':'ೀ','ु':'ು','ू':'ೂ',
      'ृ':'ೃ','े':'ೇ','ै':'ೈ','ो':'ೋ','ौ':'ೌ',
      'ं':'ಂ','ः':'ಃ','्':'್',
      '०':'೦','१':'೧','२':'೨','३':'೩','४':'೪',
      '५':'೫','६':'೬','७':'೭','८':'೮','९':'೯'
    },
    ta: {
      'अ':'அ','आ':'ஆ','इ':'இ','ई':'ஈ','उ':'உ','ऊ':'ஊ',
      'ऋ':'ரு','ॠ':'ரூ',
      'ए':'ஏ','ऐ':'ஐ','ओ':'ஓ','औ':'ஔ',
      'क':'க','ख':'க','ग':'க','घ':'க','ङ':'ங',
      'च':'ச','छ':'ச','ज':'ஜ','झ':'ஜ','ञ':'ஞ',
      'ट':'ட','ठ':'ட','ड':'ட','ढ':'ட','ण':'ண',
      'त':'த','थ':'த','द':'த','ध':'த','न':'ந',
      'प':'ப','फ':'ப','ब':'ப','भ':'ப','म':'ம',
      'य':'ய','र':'ர','ल':'ல','ळ':'ழ','व':'வ',
      'श':'ஶ','ष':'ஷ','स':'ஸ','ह':'ஹ',
      'ा':'ா','ि':'ி','ी':'ீ','ु':'ு','ू':'ூ',
      'ृ':'ு','ॄ':'ூ',
      'े':'ே','ै':'ை','ो':'ோ','ौ':'ௌ',
      'ं':'ம்','ः':':','ँ':'ம்','्':'்','ॐ':'ௐ','ऽ':'ௗ',
      '०':'0','१':'1','२':'2','३':'3','४':'4',
      '५':'5','६':'6','७':'7','८':'8','९':'9'
    }
  };

  var PRE = [
    [ /ङ्क/g, 'ंक' ], [ /ङ्ख/g, 'ंख' ], [ /ङ्ग/g, 'ंग' ], [ /ङ्घ/g, 'ंघ' ],
    [ /ञ्च/g, 'ंच' ], [ /ञ्ज/g, 'ंज' ], [ /ण्ट/g, 'ंट' ], [ /ण्ड/g, 'ंड' ],
    [ /न्त/g, 'ंत' ], [ /न्द/g, 'ंद' ], [ /म्ब/g, 'ंब' ], [ /म्भ/g, 'ंभ' ]
  ];

  function transliterateText( text, script ) {
    if ( script === 'en' ) return devanagariToIAST( text );
    var map = SCRIPT_MAP[ script ];
    if ( !map ) return text;
    var t = text;
    PRE.forEach( function ( p ) { t = t.replace( p[ 0 ], p[ 1 ] ); } );
    return Array.from( t ).map( function ( ch ) {
      return map[ ch ] !== undefined ? map[ ch ] : ch;
    } ).join( '' );
  }

  // ── Tag all transliteratable text nodes once per page ───────────
  var translatableSpans = [];

  function tagTextNodes() {
    var content = document.querySelector( '.mw-parser-output' );
    if ( content ) {
      var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT );
      var nodes  = [];
      while ( walker.nextNode() ) nodes.push( walker.currentNode );

      nodes.forEach( function ( node ) {
        var p = node.parentNode;
        if ( !p ) return;
        if ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) return;
        if ( p.closest ) {
          if ( p.closest( '.gr-controls' )    ) return;
          if ( p.closest( '.mw-editsection' ) ) return;
        }
        var orig = node.textContent;
        if ( !orig.trim() ) return;
        var span = document.createElement( 'span' );
        span.setAttribute( 'data-deva', orig );
        span.textContent = orig;
        p.replaceChild( span, node );
        translatableSpans.push( span );
      } );
    }

    document.querySelectorAll( '.vector-toc .vector-toc-text' ).forEach( function ( span ) {
      if ( span.hasAttribute( 'data-deva' ) ) return;
      var orig = span.textContent;
      if ( !orig.trim() ) return;
      span.setAttribute( 'data-deva', orig );
      translatableSpans.push( span );
    } );
  }

  // ── Apply a script to all tagged spans ─────────────────────────
  function applyScript( script ) {
    currentScript = script;
    translatableSpans.forEach( function ( span ) {
      if ( !span.parentNode ) return;
      var orig = span.getAttribute( 'data-deva' );
      if ( !orig ) return;
      span.textContent = ( script === 'deva' )
        ? orig
        : transliterateText( orig, script );
    } );
  }

  // ── Pages where sidebar TOC should not be modified ──────────────
  function _isNoTocPage() {
    var pn = ( window.mw && mw.config && mw.config.get( 'wgPageName' ) ) || '';
    return pn === 'Main_Page' || /^[A-Za-z0-9_]+:About$/.test( pn );
  }

  // ── TOC: rename "Contents" → "विषयसूची" ────────────────────────
function renameTocTitle() {
  if ( _isNoTocPage() ) return;

  var toc = document.querySelector('.vector-toc');
  if ( !toc ) return;

  var titleEl =
    toc.querySelector('.vector-toc-title') ||
    toc.querySelector('.vector-pinnable-header-label');

  if ( !titleEl ) return;

  var LABEL = 'विषयसूची';

  var span = titleEl.querySelector('.gr-toc-title');

  if ( !span ) {
    titleEl.innerHTML = '';

    span = document.createElement('span');
    span.className = 'gr-toc-title';
    span.setAttribute('data-deva', LABEL);

    titleEl.appendChild(span);
    translatableSpans.push(span);
  }

  span.textContent =
    currentScript === 'deva'
      ? LABEL
      : transliterateText(LABEL, currentScript);
}
  // ── TOC: Remove the "Beginning" / top-of-page link ─────────────
  function removeTocBeginning() {
    if ( _isNoTocPage() ) return;
    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;
    var el = toc.querySelector( '#vector-toc-beginning' );
    if ( !el ) {
      var items = toc.querySelectorAll( '.vector-toc-list-item' );
      for ( var i = 0; i < items.length; i++ ) {
        var a = items[ i ].querySelector( 'a' );
        if ( a ) {
          var href = a.getAttribute( 'href' ) || '';
          if ( href.indexOf( '#' ) === -1 ) { el = items[ i ]; break; }
        }
      }
    }
    if ( el && el.parentNode ) el.parentNode.removeChild( el );
  }

  // ── TOC: Expand all collapsed subsections on load ───────────────
  function expandTocSections() {
    if ( _isNoTocPage() ) return;
    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;
    toc.querySelectorAll( '.vector-toc-list-item-collapsed' ).forEach( function ( li ) {
      li.classList.remove( 'vector-toc-list-item-collapsed' );
    } );
  }

  // ── TOC: Inject मूल / उल्लेख nav links ─────────────────────────
  function injectTocDocNav() {
    if ( _isNoTocPage() ) return;
    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;
    if ( document.getElementById( 'gr-toc-doc-nav' ) ) return;

    var artPath   = ( window.mw && mw.config && mw.config.get( 'wgArticlePath' ) ) || '/wiki/$1';
    var pageTitle = ( window.mw && mw.config && mw.config.get( 'wgPageName'   ) ) || '';

    var teekaPage   = document.querySelector( '.gr-teeka-page' );
    var primarySlug = teekaPage ? ( teekaPage.getAttribute( 'data-primary' ) || '' ) : '';
    var docSlug     = teekaPage ? ( teekaPage.getAttribute( 'data-slug'    ) || '' ) : '';

    if ( !primarySlug ) {
      primarySlug = pageTitle.split( '/' )[ 0 ];
      docSlug     = primarySlug;
    }
    if ( !primarySlug ) return;

    function wikiUrl( slug ) {
      if ( window.mw && mw.util && mw.util.getUrl ) return mw.util.getUrl( slug );
      return artPath.replace( '$1', encodeURIComponent( slug ).replace( /%2F/g, '/' ) );
    }

    var moolaUrl   = wikiUrl( primarySlug );
    var ullekhaUrl = wikiUrl( primarySlug + '/Ullekha' );

    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 moolaPageUrl = wikiUrl( primarySlug + '/Moola' );

    var showMoolaPage  = !teekaPage && hasMoolaPage;
    var showMoolaBack  = !!teekaPage;
    var showUllekha    = hasUllekhaPage || !!teekaPage;

    if ( !showMoolaPage && !showMoolaBack && !showUllekha ) return;

    var nav = document.createElement( 'div' );
    nav.id = 'gr-toc-doc-nav';
    nav.setAttribute( 'class', 'toc-main-links');

    function makeBtn( href, label ) {
      var a = document.createElement( 'a' );
      a.href = href;
      a.setAttribute( 'class', 'toc-main-link-item');
      var lspan = document.createElement( 'span' );
      lspan.setAttribute( 'data-deva', label );
      lspan.textContent = ( currentScript && currentScript !== 'deva' )
        ? transliterateText( label, currentScript )
        : label;
      translatableSpans.push( lspan );
      a.appendChild( lspan );
      a.addEventListener( 'mouseover', function () { this.style.opacity = '0.72'; } );
      a.addEventListener( 'mouseout',  function () { this.style.opacity = '1';    } );
      return a;
    }

    if ( showMoolaPage ) nav.appendChild( makeBtn( moolaPageUrl, 'मूलम्' ) );
    if ( showMoolaBack ) nav.appendChild( makeBtn( moolaUrl,     'मूल' ) );
    if ( showUllekha   ) nav.appendChild( makeBtn( ullekhaUrl,   'उल्लेख') );

    var tocContents = toc.querySelector( '.vector-toc-contents' );
    if ( tocContents ) toc.insertBefore( nav, tocContents );
    else               toc.appendChild( nav );
  }

  // ── TOC active-item highlight ────────────────────────────────────
  function watchTocActive() {
    if ( _isNoTocPage() ) return;
    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;
    if ( !window.MutationObserver ) return;

    if ( toc._grObserved ) {
      toc.querySelectorAll( '.vector-toc-list-item' ).forEach( attachHighlight );
      return;
    }
    toc._grObserved = true;

    var ACTIVE_COLOR = '#f57c00';

    function setActive( li, on ) {
      var link = li.querySelector( '.vector-toc-link' ) || li.querySelector( 'a' );
      if ( !link ) return;
      var hasActiveChild = !!li.querySelector(
        '.vector-toc-list-item .vector-toc-list-item-active'
      );
      var shouldHighlight = on && !hasActiveChild;
      if ( shouldHighlight ) {
        link.style.setProperty( 'color',       ACTIVE_COLOR, 'important' );
        link.style.setProperty( 'font-weight', '700',        'important' );
        link.querySelectorAll( '*' ).forEach( function ( el ) {
          el.style.setProperty( 'color',       ACTIVE_COLOR, 'important' );
          el.style.setProperty( 'font-weight', '700',        'important' );
        } );
      } else {
        link.style.removeProperty( 'color' );
        link.style.setProperty( 'font-weight', '400', 'important' );
        link.querySelectorAll( '*' ).forEach( function ( el ) {
          el.style.removeProperty( 'color' );
          el.style.setProperty( 'font-weight', '400', 'important' );
        } );
      }
    }

    function normaliseAll() {
      toc.querySelectorAll( '.vector-toc-list-item' ).forEach( function ( li ) {
        setActive( li, li.classList.contains( 'vector-toc-list-item-active' ) );
      } );
    }

    function attachHighlight( li ) {
      if ( li._grHighlightAttached ) return;
      li._grHighlightAttached = true;
      liObs.observe( li, { attributes: true, attributeFilter: [ 'class' ] } );
      setActive( li, li.classList.contains( 'vector-toc-list-item-active' ) );
    }

    var liObs = new MutationObserver( function ( mutations ) {
      mutations.forEach( function ( m ) {
        if ( m.attributeName !== 'class' ) return;
        var li     = m.target;
        var active = li.classList.contains( 'vector-toc-list-item-active' );
        setActive( li, active );

        if ( active ) {
          var anc = li.parentNode;
          while ( anc && anc !== document.body ) {
            if ( anc.classList &&
                 anc.classList.contains( 'vector-toc-list-item' ) &&
                 anc.classList.contains( 'vector-toc-list-item-collapsed' ) ) {
              anc.classList.remove( 'vector-toc-list-item-collapsed' );
            }
            anc = anc.parentNode;
          }

          var container = document.querySelector( '.vector-sticky-pinned-container' );
          if ( container ) {
            var vis = container.offsetHeight > 0 &&
                      container.offsetParent !== null &&
                      window.getComputedStyle( container ).display !== 'none' &&
                      window.getComputedStyle( container ).visibility !== 'hidden';
            if ( vis ) {
              var lr = li.getBoundingClientRect();
              var cr = container.getBoundingClientRect();
              if ( lr.top < cr.top + 4 || lr.bottom > cr.bottom - 4 ) {
                var sh = null, node = li.parentNode;
                while ( node && node !== document.body ) {
                  var ov = window.getComputedStyle( node ).overflowY;
                  if ( ( ov === 'auto' || ov === 'scroll' ) &&
                       node.scrollHeight > node.clientHeight ) { sh = node; break; }
                  node = node.parentNode;
                }
                if ( !sh && container.scrollHeight > container.clientHeight ) sh = container;
                if ( sh ) {
                  var hr  = sh.getBoundingClientRect();
                  var top = lr.top - hr.top + sh.scrollTop;
                  sh.scrollTop = Math.max( 0, top - sh.clientHeight / 2 + li.offsetHeight / 2 );
                }
              }
            }
          }
        }
      } );
    } );

    var structObs = new MutationObserver( function ( mutations ) {
      mutations.forEach( function ( m ) {
        if ( m.type !== 'childList' ) return;
        m.addedNodes.forEach( function ( n ) {
          if ( n.nodeType !== 1 ) return;
          if ( n.classList && n.classList.contains( 'vector-toc-list-item' ) ) attachHighlight( n );
          if ( n.querySelectorAll ) n.querySelectorAll( '.vector-toc-list-item' ).forEach( attachHighlight );
          var newSpans = [];
          if ( n.classList && n.classList.contains( 'vector-toc-text' ) ) newSpans.push( n );
          if ( n.querySelectorAll ) n.querySelectorAll( '.vector-toc-text' ).forEach( function ( s ) { newSpans.push( s ); } );
          newSpans.forEach( function ( span ) {
            if ( span.hasAttribute( 'data-deva' ) ) return;
            var orig = span.textContent;
            if ( !orig.trim() ) return;
            span.setAttribute( 'data-deva', orig );
            if ( currentScript !== 'deva' ) span.textContent = transliterateText( orig, currentScript );
            translatableSpans.push( span );
          } );
        } );
      } );
    } );

    toc.querySelectorAll( '.vector-toc-list-item' ).forEach( attachHighlight );
    structObs.observe( toc, { childList: true, subtree: true } );

    setTimeout( function () {
      normaliseAll();
      var active = toc.querySelector( '.vector-toc-list-item-active' );
      if ( active ) setActive( active, true );
    }, 300 );
  }

  // ── TOC: Use Vector's native TOC, add highlight + expand + rename ──────
  function setupToc() {
    if ( _isNoTocPage() ) return;
    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;

    removeTocBeginning();
    renameTocTitle();
    expandTocSections();
    injectTocDocNav();
    attachHeadingObserver();
  }

  // ── IntersectionObserver on actual heading elements ──────────────
  var _headingObserver = null;

  function attachHeadingObserver() {
    if ( _isNoTocPage() ) return;
    if ( _headingObserver ) return;
    if ( !window.IntersectionObserver ) {
      return; /* IntersectionObserver not available — skip active highlight */
    }

    var ACTIVE_COLOR = '#f57c00';
    var _activeId    = null;

    var content  = document.querySelector( '.mw-parser-output' );
    if ( !content ) return;
    var headings = Array.from(
      content.querySelectorAll( 'h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]' )
    );
    if ( !headings.length ) return;

    var toc = document.querySelector( '.vector-toc' );
    if ( !toc ) return;

    function getTocLink( id ) {
      return toc.querySelector( 'a[href="#' + CSS.escape( id ) + '"]' );
    }

    function getTocLi( id ) {
      var a = getTocLink( id );
      return a ? a.closest( '.vector-toc-list-item' ) : null;
    }

    function clearActive() {
      /* 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' );
        var lnk = li.querySelector( '.vector-toc-link' );
        if ( !lnk ) return;
        lnk.style.removeProperty( 'color' );
        lnk.style.setProperty( 'font-weight', '400', 'important' );
        lnk.querySelectorAll( '*' ).forEach( function ( el ) {
          el.style.removeProperty( 'color' );
          el.style.setProperty( 'font-weight', '400', 'important' );
        } );
      } );
    }

    function setActive( id ) {
      if ( _activeId === id ) return;
      _activeId = id;
      clearActive();
      if ( !id ) return;

      var li = getTocLi( id );
      if ( !li ) return;

      li.classList.add( 'vector-toc-list-item-active' );

      var hasActiveChild = !!li.querySelector(
        '.vector-toc-list-item .vector-toc-list-item-active'
      );
      if ( !hasActiveChild ) {
        var lnk = li.querySelector( '.vector-toc-link' );
        if ( lnk ) {
          lnk.style.setProperty( 'color',       ACTIVE_COLOR, 'important' );
          lnk.style.setProperty( 'font-weight', '700',        'important' );
          lnk.querySelectorAll( '*' ).forEach( function ( el ) {
            el.style.setProperty( 'color',       ACTIVE_COLOR, 'important' );
            el.style.setProperty( 'font-weight', '700',        'important' );
          } );
        }
      }

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

      // Scroll TOC item into view within sidebar
      var sticky = document.querySelector( '.vector-sticky-pinned-container' );
      var scrollEl = sticky || toc;
      if ( scrollEl.scrollHeight > scrollEl.clientHeight ) {
        var lr = li.getBoundingClientRect();
        var cr = scrollEl.getBoundingClientRect();
        if ( lr.top < cr.top + 8 || lr.bottom > cr.bottom - 8 ) {
          scrollEl.scrollTop += lr.top - cr.top - scrollEl.clientHeight / 2 + li.offsetHeight / 2;
        }
      }
    }

    var _visible = new Set();

    _headingObserver = new IntersectionObserver( function ( entries ) {
      entries.forEach( function ( entry ) {
        if ( entry.isIntersecting ) {
          _visible.add( entry.target.id );
        } else {
          _visible.delete( entry.target.id );
        }
      } );

      var topId = null;
      var topY  = Infinity;
      _visible.forEach( function ( id ) {
        var el = document.getElementById( id );
        if ( el ) {
          var y = el.getBoundingClientRect().top;
          if ( y >= 0 && y < topY ) { topY = y; topId = id; }
        }
      } );

      if ( !topId ) {
        var bestY = -Infinity;
        headings.forEach( function ( h ) {
          var y = h.getBoundingClientRect().top;
          if ( y < 0 && y > bestY ) { bestY = y; topId = h.id; }
        } );
      }

      setActive( topId || null );
    }, {
      rootMargin: '-60px 0px -65% 0px',
      threshold: 0
    } );

    headings.forEach( function ( h ) { _headingObserver.observe( h ); } );
  }

  // ── Init ────────────────────────────────────────────────────────
  function init() {
    var HIDE_IDS = [
      'vector-appearance',
      'vector-appearance-pinned-container',
      'vector-appearance-unpinned-container'
    ];
    function removeHiddenEls() {
      HIDE_IDS.forEach( function ( id ) {
        var el = document.getElementById( id );
        if ( el && el.parentNode ) el.parentNode.removeChild( el );
      } );
      var pt = document.getElementById( 'vector-page-tools' ) ||
               document.querySelector( '.vector-page-tools-pinned-container' );
      if ( pt ) {
        pt.querySelectorAll( '[aria-controls="vector-appearance"]' )
          .forEach( function ( el ) { if ( el.parentNode ) el.parentNode.removeChild( el ); } );
      }
    }
    removeHiddenEls();

    ( function detectTeekaMode() {
      var tp = document.querySelector( '.gr-teeka-page' );
      if ( !tp ) return;
      var primary = tp.getAttribute( 'data-primary' ) || '';
      var artPath = ( window.mw && mw.config.get( 'wgArticlePath' ) ) || '/wiki/$1';
      var mainUrl = artPath.replace( '$1', primary );
      var refParam = window.location.search.match( /[?&]ref=([01])/ );
      if ( refParam ) {
        document.body.classList.add( refParam[1] === '1' ? 'gr-ref-mode' : 'gr-standalone' );
        return;
      }
      var ref = document.referrer || '';
      document.body.classList.add(
        ( ref && primary && ref.indexOf( mainUrl ) !== -1 ) ? 'gr-ref-mode' : 'gr-standalone'
      );
    }() );

    if ( window.MutationObserver ) {
      var hideObs = new MutationObserver( function ( mutations ) {
        var dirty = false;
        mutations.forEach( function ( m ) { if ( m.addedNodes.length ) dirty = true; } );
        if ( dirty ) removeHiddenEls();
      } );
      hideObs.observe( document.body, { childList: true, subtree: false } );
      setTimeout( function () { hideObs.disconnect(); }, 6000 );
    }

    var content       = document.querySelector( '.mw-parser-output' );
    var alreadyTagged = content && content.querySelector( '[data-deva]' );
    if ( !alreadyTagged ) {
      translatableSpans = [];
      tagTextNodes();
    } else {
      document.querySelectorAll( '.vector-toc .vector-toc-text:not([data-deva])' ).forEach( function ( span ) {
        var orig = span.textContent;
        if ( !orig.trim() ) return;
        span.setAttribute( 'data-deva', orig );
        translatableSpans.push( span );
      } );
    }

    var saved = ( function () {
      try { return localStorage.getItem( LS_SCRIPT_KEY ); } catch ( e ) { return null; }
    }() );
    if ( saved && saved !== 'deva' ) { applyScript( saved ); }
    else { currentScript = 'deva'; }

    setTimeout( setupToc, 200 );
  }

  // ── React to toolbar script-change events ──────────────────────
  window.addEventListener( 'gr-script-change', function ( e ) {
    var script = e && e.detail && e.detail.script;
    if ( script ) applyScript( script );
  } );

  // ── React to gr-new-content (siteNav panel rendered new items) ──
  window.addEventListener( 'gr-new-content', function ( e ) {
    var container = e && e.detail && e.detail.container;
    if ( !container ) return;
    var walker = document.createTreeWalker( container, NodeFilter.SHOW_TEXT );
    var nodes = [];
    while ( walker.nextNode() ) nodes.push( walker.currentNode );
    nodes.forEach( function ( node ) {
      var p = node.parentNode;
      if ( !p || ( p.hasAttribute && p.hasAttribute( 'data-deva' ) ) ) return;
      var orig = node.textContent;
      if ( !orig.trim() ) return;
      var span = document.createElement( 'span' );
      span.setAttribute( 'data-deva', orig );
      span.textContent = currentScript !== 'deva' ? transliterateText( orig, currentScript ) : orig;
      p.replaceChild( span, node );
      translatableSpans.push( span );
    } );
  } );

  // ── React to script changes from OTHER tabs (BroadcastChannel) ──
  try {
    var _grBC = new BroadcastChannel( 'gr-script' );
    _grBC.onmessage = function ( e ) {
      var script = e && e.data && e.data.script;
      if ( script ) {
        currentScript = script;
        var sel = document.querySelector( '.gr-script-sel' );
        if ( sel ) sel.value = script;
        applyScript( script );
      }
    };
  } catch ( e ) {}

  // ── MediaWiki SPA-style navigation ──────────────────────────────
  if ( window.mw ) {
    mw.hook( 'wikipage.content' ).add( function () {
      setTimeout( function () {
        var content       = document.querySelector( '.mw-parser-output' );
        var alreadyTagged = content && content.querySelector( '[data-deva]' );
        if ( !alreadyTagged ) {
          translatableSpans = [];
          tagTextNodes();
        } else {
          document.querySelectorAll( '.vector-toc .vector-toc-text:not([data-deva])' ).forEach( function ( span ) {
            var orig = span.textContent;
            if ( !orig.trim() ) return;
            span.setAttribute( 'data-deva', orig );
            translatableSpans.push( span );
          } );
        }
        if ( currentScript !== 'deva' ) applyScript( currentScript );
        setupToc();
      }, 150 );
    } );
  }

  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', init );
  } else {
    init();
  }

}() );   /* ← end of main IIFE */


// ── Inject "Help" and "About" links into the header ─────────────────
( function () {
  function wikiHref( title ) {
    if ( window.mw && mw.util && mw.util.getUrl ) return mw.util.getUrl( title );
    var ap = ( window.mw && mw.config && mw.config.get( 'wgArticlePath' ) ) || '/wiki/$1';
    return ap.replace( '$1', title );
  }

  var linkStyle = [
    'color:rgba(255,255,255,0.88)', 'font-size:0.88em',
    'font-family:system-ui,sans-serif', 'font-weight:500',
    'text-decoration:none', 'padding:4px 10px', 'border-radius:4px',
    'margin-right:4px', 'transition:color 0.15s,background 0.15s',
    'white-space:nowrap',
  ].join( ';' );

  function makeHeaderLink( id, href, label ) {
    var a = document.createElement( 'a' );
    a.id = id; a.href = href; a.textContent = label;
    a.style.cssText = linkStyle;
    a.addEventListener( 'mouseover', function () {
      this.style.color = '#fff'; this.style.background = 'rgba(255,255,255,0.12)';
    } );
    a.addEventListener( 'mouseout', function () {
      this.style.color = 'rgba(255,255,255,0.88)'; this.style.background = 'transparent';
    } );
    return a;
  }

  function injectHeaderLinks() {
    if ( document.getElementById( 'gr-about-link' ) ) return;

    var headerEnd = document.querySelector( '.vector-header-end' ) ||
                    document.querySelector( '#vector-user-links' ) ||
                    document.querySelector( '.mw-header' );
    if ( !headerEnd ) return;

    var helpLink  = makeHeaderLink( 'gr-help-link',  wikiHref( 'My_wiki:Help'  ), 'Help'  );
    var aboutLink = makeHeaderLink( 'gr-about-link', wikiHref( 'My_wiki:About' ), 'About' );

    var ul = document.querySelector( '.vector-user-links' ) ||
             document.querySelector( '#pt-userpage' );
    if ( ul && ul.parentNode === headerEnd ) {
      headerEnd.insertBefore( aboutLink, ul );
      headerEnd.insertBefore( helpLink,  aboutLink );
    } else {
      headerEnd.appendChild( helpLink );
      headerEnd.appendChild( aboutLink );
    }
  }

  if ( document.readyState === 'loading' ) document.addEventListener( 'DOMContentLoaded', injectHeaderLinks );
  else injectHeaderLinks();
}() );


// ── Main page: by-Grantha / by-Author toggle ──────────────────────
( function () {
  function grHomeView( v ) {
    var gView = document.getElementById( 'gr-view-grantha' );
    var aView = document.getElementById( 'gr-view-author' );
    var gBtn  = document.getElementById( 'gr-toggle-grantha' );
    var aBtn  = document.getElementById( 'gr-toggle-author' );
    if ( !gView || !aView || !gBtn || !aBtn ) return;
    gView.style.display = ( v === 'grantha' ) ? '' : 'none';
    aView.style.display = ( v === 'author'  ) ? '' : 'none';
    gBtn.className = 'gr-toggle-btn' + ( v === 'grantha' ? ' gr-toggle-active' : '' );
    aBtn.className = 'gr-toggle-btn' + ( v === 'author'  ? ' gr-toggle-active' : '' );
    try { localStorage.setItem( 'gr_home_view', v ); } catch ( e ) {}
  }

  function initHomeToggle() {
    var gBtn = document.getElementById( 'gr-toggle-grantha' );
    var aBtn = document.getElementById( 'gr-toggle-author' );
    if ( !gBtn || !aBtn ) return;
    gBtn.addEventListener( 'click', function () { grHomeView( 'grantha' ); } );
    aBtn.addEventListener( 'click', function () { grHomeView( 'author' );  } );
    [ gBtn, aBtn ].forEach( function ( btn ) {
      btn.addEventListener( 'keydown', function ( e ) {
        if ( e.key === 'Enter' || e.key === ' ' ) btn.click();
      } );
    } );
    var saved;
    try { saved = localStorage.getItem( 'gr_home_view' ); } catch ( e ) {}
    if ( saved === 'author' ) grHomeView( 'author' );
  }

  if ( document.readyState === 'loading' ) document.addEventListener( 'DOMContentLoaded', initHomeToggle );
  else initHomeToggle();
}() );


// ── Ullekha reference link handler ─────────────────────────────────
( function () {

  function highlightOnArrival() {
    var search = window.location.search;
    if ( !search ) return;
    var m = search.match( /[?&]hlUllekha=([^&]+)/ );
    if ( !m ) return;
    var needle;
    try { needle = decodeURIComponent( m[ 1 ] ); } catch ( e ) { return; }
    if ( !needle || needle.length < 4 ) return;

    function doHighlight() {
      var content = document.querySelector( '.mw-parser-output' );
      if ( !content ) return;

      var snippet = needle.slice( 0, 40 );
      var found   = false;

      var spans = content.querySelectorAll( '[data-deva]' );
      for ( var i = 0; i < spans.length && !found; i++ ) {
        var spanEl = spans[ i ];
        var orig   = spanEl.getAttribute( 'data-deva' ) || '';
        if ( orig.indexOf( snippet ) === -1 ) continue;

        var idx    = orig.indexOf( snippet );
        var hlText = orig.slice( idx, Math.min( idx + needle.length, orig.length ) );
        var mark   = document.createElement( 'mark' );
        mark.className  = 'gr-ullekha-highlight';
        mark.style.cssText = 'background:#fff176;border-radius:2px;padding:0 2px;';
        mark.textContent   = hlText;

        var parent = spanEl.parentNode;
        if ( !parent ) continue;

        var before = document.createTextNode( orig.slice( 0, idx ) );
        var after  = document.createTextNode( orig.slice( idx + hlText.length ) );
        parent.insertBefore( before, spanEl );
        parent.insertBefore( mark,   spanEl );
        parent.insertBefore( after,  spanEl );
        parent.removeChild( spanEl );

        setTimeout( function () {
          mark.scrollIntoView( { behavior: 'smooth', block: 'center' } );
        }, 100 );
        found = true;
      }

      if ( !found ) {
        var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT );
        while ( walker.nextNode() && !found ) {
          var node = walker.currentNode;
          var txt  = node.textContent || '';
          if ( txt.indexOf( snippet ) === -1 ) continue;
          var idx2   = txt.indexOf( snippet );
          var mark2  = document.createElement( 'mark' );
          mark2.className    = 'gr-ullekha-highlight';
          mark2.style.cssText = 'background:#fff176;border-radius:2px;padding:0 2px;';
          mark2.textContent  = txt.slice( idx2, Math.min( idx2 + needle.length, txt.length ) );
          var p = node.parentNode;
          p.insertBefore( document.createTextNode( txt.slice( 0, idx2 ) ), node );
          p.insertBefore( mark2, node );
          p.insertBefore( document.createTextNode( txt.slice( idx2 + mark2.textContent.length ) ), node );
          p.removeChild( node );
          setTimeout( function () {
            mark2.scrollIntoView( { behavior: 'smooth', block: 'center' } );
          }, 100 );
          found = true;
        }
      }
    }

    setTimeout( doHighlight, 600 );
  }

  function wireUllekhaLinks() {
    document.querySelectorAll( '.gr-ullekha-ref-link' ).forEach( function ( wrap ) {
      var anchor = wrap.getAttribute( 'data-anchor' ) || '';
      var hl     = wrap.getAttribute( 'data-hl' )     || '';
      var a = wrap.querySelector( 'a' );
      if ( !a ) return;

      var base = a.href.split( '#' )[ 0 ];
      var encoded = encodeURIComponent( hl );
      a.href = base + ( hl ? '?hlUllekha=' + encoded : '' )
                    + ( anchor ? '#' + anchor : '' );
    } );
  }

  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', function () {
      highlightOnArrival();
      wireUllekhaLinks();
    } );
  } else {
    highlightOnArrival();
    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 = [400, 900, 1500];
    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-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>';
      bar.appendChild( nav );
    }

    var dismiss = isMob
      ? bar.querySelector( '#gr-hl-dismiss' )
      : ( function() {
          var b = document.createElement( 'button' );
          b.textContent = '✕ Clear';
          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' );
    if ( nextBtn ) nextBtn.onclick = function() { goTo( currentIdx + 1 ); };
    if ( prevBtn ) prevBtn.onclick = function() { goTo( currentIdx - 1 ); };
  }

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

}() );