Jump to content
Go to Vaniquotes | Go to Vanipedia | Go to Vanimedia


Vanisource - the complete essence of Vedic knowledge


MediaWiki:Common.js: Difference between revisions

From Vanisource
Yadasampati (talk | contribs)
Blanked the page
Tags: Blanking Manual revert
No edit summary
Line 1: Line 1:
/* Any JavaScript here will be loaded for all users on every page load. */


if (mw.config.get('wgPageName' ) === 'Krishna_says_in_Bhagavad-gita') {
  /* switch tooltip texts from content-div to label-div */
  const v_labels = Array.from(document.getElementsByClassName("tabs-label"));
  const v_content = Array.from(document.getElementsByClassName("tabs-content"));
  for (var i = 0; i < v_labels.length; i++) {
    v_labels[i].title = v_content[i].title;
    v_content[i].title = "";
  }
  /* adjust wikitables */
  var arr = Array.from(document.getElementsByClassName("wikitable"));
  for (var t = 0; t < arr.length; t++) {
    /* insert THEAD in wikitable */
    var thead = document.createElement("thead");
    thead.style.display = "block";
    var wtbl = arr[t];
    wtbl.insertBefore(thead, wtbl.firstChild);
    /* move first row of TBODY to THEAD */
    thead.appendChild(wtbl.rows[0]);
    /* make TBODY scrollable */
    var tbody = wtbl.tBodies[0];
    tbody.style.display = "block";
    tbody.style.overflowY = "auto";
    tbody.style.maxHeight = "500px";   
    for (var r = 0; r < wtbl.rows.length; r++) {
      var row = wtbl.rows[r];
      /* adjust column widths and hide from-until columns */
      row.cells[0].classList = "cw_0";
      row.cells[1].classList = "cw_150";
      row.cells[2].classList = "cw_500";
      /* mark the text */
      if (r == 0) continue;
      var v_text = row.cells[2].textContent;
      var v_html = ""; var v_from = 0;
      var marks = JSON.parse(row.cells[0].textContent.replaceAll("O", "[").replaceAll("C", "]"));
      for (var m = 0; m < marks.length; m++) {
        v_html +=
          v_text.substr(v_from,marks[m][0]-v_from) + "<mark>" +
          v_text.substr(marks[m][0],marks[m][1]-marks[m][0]+1) + "</mark>";
        v_from = marks[m][1]+1;
      }
      v_html += v_text.substr(v_from);
      row.cells[2].innerHTML = v_html;
    }
  }
}
// install Google Search
// document.write('<script type="text/javascript" src="'
// + 'http://vanipedia.org/w/index.php?title=MediaWiki:GoogleSearch.js'
// + '&action=raw&ctype=text/javascript"></' + 'script>');
// install JQuery
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:JQuery.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install WikEd toolbar and Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vaniquotes.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Replace
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:TextboxReplace.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Unify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Unify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vanify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install Vani_suggest for diacritics auto-suggest pulldown on the search bar
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vani_suggest.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
// install previous/next links for video categories in all languages
//document.write('<script type="text/javascript" src="/navcat/navcat.js"></' + 'script>');
// Buttons
//addOnloadHook(function() {
//if (document.editform) {
//addPortletLink("p-cactions", "javascript:Unify()", "unify", "ca-unify", "Convert Balarama fonts to Unicode", "");
// addPortletLink("p-cactions", "javascript:Vanify()", "vanify", "ca-vanify", "Corrects vani markup", "");
// addPortletLink('p-cactions', 'javascript:format()', 'format', 'ca-format', 'Format article', '');
//addPortletLink('p-cactions', 'javascript:wpTextboxReplace()', 'Replace', 'ca-replace', 'Replaces text in the edit window', 'R');
//}
//});
// end Buttons
// --- YouTube Random Shorts (Kasya) ---
$(document).ready(function() {
    var $widget = $('.vani-shorts-viewport');
   
    if ($widget.length) {
        var $viewport = $widget;
        var $titleFrame = $widget.find('.vani-shorts-title');
       
        new mw.Api().get({
            action: 'query',
            prop: 'revisions',
            titles: 'VanimediaMayapur YouTube SHORTS List',
            rvprop: 'content',
            format: 'json'
        }).done(function(data) {
            try {
                var pages = data.query.pages;
                var pageId = Object.keys(pages)[0];
               
                if (pageId == "-1" || !pages[pageId].revisions) {
                    $viewport.text("Database list page not found.");
                    return;
                }
               
                // FIXED: Corrected MediaWiki revision array syntax lookup
                var rawWikiText = pages[pageId].revisions[0]['*'];
                var lines = rawWikiText.split('\n');
                var validShorts = [];
               
                for (var i = 0; i < lines.length; i++) {
                    var line = $.trim(lines[i]);
                    if (line.indexOf('File:') === 0 && line.toLowerCase().indexOf('link=') !== -1) {
                        validShorts.push(line);
                    }
                }
               
                if (validShorts.length === 0) {
                    $viewport.text("No gallery entries recognized.");
                    return;
                }
               
                var randomLine = validShorts[Math.floor(Math.random() * validShorts.length)];
                var parts = randomLine.split('|');
               
                // Reset defaults before parsing line elements
                var rawUrl = "";
               
                for (var j = 0; j < parts.length; j++) {
                    var part = $.trim(parts[j]);
                   
                    if (part.toLowerCase().indexOf('link=') === 0) {
                        rawUrl = part.substring(5).trim();
                    }
                }
               
                // Clean all template formatting brackets, space characters, and hidden control markers
                var urlCleaned = rawUrl.replace(/[\u200e\u200f\s\}\}\]]/g, '');
               
                // Direct YouTube Alphanumeric Pattern Matcher
                var idRegex = /(?:shorts\/|v=|embed\/|\/)([a-zA-Z0-9_-]{11})/;
                var matchResults = urlCleaned.match(idRegex);
                var videoId = "";
               
                if (matchResults && matchResults[1]) {
                    // FIXED: Grabbed index 1 to pull the standalone 11-digit ID without any slashes
                    videoId = matchResults[1].trim();
                }
               
                if (videoId && videoId.length === 11) {
                    // FIXED: Isolated to inject ONLY the video player iframe at the correct 280px width
                  var embedUrl = "https://www.youtube.com/embed/" + videoId;
                    $viewport.html('<iframe width="280" height="225" src="' + embedUrl + '" frameborder="0" allowfullscreen style="border-radius: 8px; border: none;"></iframe>');
                } else {
                    $viewport.html("<div style='color:#c0392b; padding:10px; font-size:11px;'>Invalid video ID sequence extracted.</div>");
                }
               
            } catch (err) {
                console.error("Shorts engine exception:", err);
                $viewport.text("Error handling text structure values.");
            }
        }).fail(function() {
            $viewport.text("API connection failed.");
        });
    }
});
// Kasya
/* Open Timeline links in a new tab ONLY on the Chapters page */
mw.hook('wikipage.content').add(function($content) {
    // Check if we are exactly on the timeline page
    if (mw.config.get('wgPageName') === "Chapters_in_Srila_Prabhupada's_Life") {
       
        // If yes, apply the new tab rule to the table
        $content.find('#mp-tfp a').attr('target', '_blank').attr('rel', 'noopener noreferrer');
    }
});
// Wait for MediaWiki core modules and the DOM to load
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
    $(document).ready(function () {
       
// 1. FIND THE "102 Languages" SIDEBAR SECTION
        var $sidebarMenu = $('#p-102_Languages ul').first();
       
        // Fallback in case your specific MediaWiki version formats the ID differently
        if ($sidebarMenu.length === 0) {
            $('.vector-menu-heading, .portal h3').each(function() {
                // Looks for the heading text directly
                if ($(this).text().trim() === '102 Languages') {
                    $sidebarMenu = $(this).closest('.vector-menu, .portal').find('ul').first();
                }
            });
        }
       
        // Exit if the 102 Languages menu isn't found on the page
        if (!$sidebarMenu || $sidebarMenu.length === 0) return;
        // 2. CREATE THE SEARCH BAR AS A MENU ITEM
        var $searchLi = $('<li id="n-language-search" style="margin-top: 10px; margin-bottom: 5px; list-style: none;"></li>');
        var $searchBox = $('<input type="text" id="sidebarLanguageSearch" placeholder="Search 102 Languages..." style="width: 100%; box-sizing: border-box; padding: 5px; font-size: 0.85em; border: 1px solid #a2a9b1; border-radius: 2px;" autocomplete="off" />');
       
        // Container for the results (hidden by default)
        var $resultsContainer = $('<ul id="sidebarLanguageResults" style="list-style: none; padding: 0; margin: 4px 0 0 0; max-height: 200px; overflow-y: auto; border: 1px solid #eaecf0; background: #ffffff; display: none;"></ul>');
        // Build the menu item and append it to the sidebar
        $searchLi.append($searchBox).append($resultsContainer);
        $sidebarMenu.prepend($searchLi);
        var api = new mw.Api();
        var languageList = [];
        // 3. FETCH LANGUAGES FROM THE CATEGORY VIA API IN THE BACKGROUND
        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:Languages_with_Total_Pages',
            cmlimit: 'max',
            format: 'json'
        }).done(function (data) {
            if (data.query && data.query.categorymembers) {
                languageList = data.query.categorymembers.map(function (member) {
                    return {
                        title: member.title,
                        url: mw.util.getUrl(member.title)
                    };
                });
            }
        });
        // 4. ADD SEARCH FUNCTIONALITY TO THE SIDEBAR BOX
        $('#sidebarLanguageSearch').on('keyup', function (e) {
            e.stopPropagation();
            var searchTerm = $(this).val().toLowerCase();
            $resultsContainer.empty();
            // Hide the results box if the search is empty
            if (searchTerm.length === 0) {
                $resultsContainer.hide();
                return;
            }
            // Filter the fetched languages
            var filteredLanguages = languageList.filter(function (lang) {
                return lang.title.toLowerCase().indexOf(searchTerm) > -1;
            });
            // Show results container
            $resultsContainer.show();
            // Append results using Vector's standard dark text color
            if (filteredLanguages.length > 0) {
                filteredLanguages.forEach(function (lang) {
                    $resultsContainer.append('<li style="padding: 4px 6px; border-bottom: 1px solid #f8f9fa;"><a href="' + lang.url + '" style="text-decoration: none; color: #0645ad; display: block; font-size: 0.9em;">' + lang.title + '</a></li>');
                });
            } else {
                $resultsContainer.append('<li style="padding: 4px 6px; color: #72777d; font-size: 0.9em;">No languages found</li>');
            }
        });
        // Prevent typing from accidentally triggering global keyboard shortcuts
        $('#sidebarLanguageSearch').on('keydown', function(e) {
            e.stopPropagation();
        });
       
        // Optional: Hide results when clicking outside the sidebar search
        $(document).on('click', function(e) {
            if (!$(e.target).closest('#n-language-search').length) {
                $resultsContainer.hide();
            }
        });
       
        // Re-show results if they click back into the search box
        $('#sidebarLanguageSearch').on('click', function() {
            if ($(this).val().length > 0) {
                $resultsContainer.show();
            }
        });
       
    });
});
// 5. SIDEBAR ACCORDION - STRICT LEFT-COLUMN ISOLATION & TRUE ACCORDION
(function() {
    // 1. Tag ONLY menus physically located in the left sidebar
    function tagSidebarMenus() {
        var allMenus = document.querySelectorAll('.vector-menu');
        for (var i = 0; i < allMenus.length; i++) {
            var menu = allMenus[i];
           
            // STRICT INCLUSION: Only tag menus inside the modern or legacy left sidebar containers
            if (menu.closest('#vector-main-menu, #mw-panel, #mw-navigation')) {
                menu.classList.add('vanipedia-sidebar-menu');
               
                // Collapse them by default if they have a heading
                if (menu.querySelector('.vector-menu-heading') && !menu.classList.contains('vanipedia-collapsed')) {
                    menu.classList.add('vanipedia-collapsed');
                }
            }
        }
    }
   
    // Run tagging immediately, and again on load as a failsafe
    tagSidebarMenus();
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', tagSidebarMenus);
    }
    // 2. Inject CSS targeted STRICTLY at our newly tagged custom class
    var style = document.createElement('style');
    style.textContent =
        /* Widen only our tagged sidebar menus */
        '.vanipedia-sidebar-menu { width: 185px !important; } ' +
       
        /* Core functional hiding rule */
        '.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-content { display: none !important; } ' +
       
        /* Bold, Dark Header */
        '.vanipedia-sidebar-menu .vector-menu-heading { ' +
        '  cursor: pointer !important; ' +
        '  background-color: #2c4c50 !important; ' +   
        '  border: 1px solid #1b3033 !important; ' +   
        '  border-radius: 4px !important; ' +           
        '  margin-bottom: 4px !important; ' +           
        '  padding: 10px 12px !important; ' +
        '  font-weight: 600 !important; ' +
        '  color: #ffffff !important; ' +               
        '  display: flex !important; ' +
        '  justify-content: space-between !important; ' +
        '  align-items: center !important; ' +
        '  gap: 10px !important; ' +
        '  transition: all 0.2s ease; ' +
        '} ' +
       
        /* Fix header corners when expanded */
        '.vanipedia-sidebar-menu:not(.vanipedia-collapsed) .vector-menu-heading { ' +
        '  border-radius: 4px 4px 0 0 !important; ' +
        '  margin-bottom: 0 !important; ' +
        '} ' +
       
        /* Hover effect */
        '.vanipedia-sidebar-menu .vector-menu-heading:hover { background-color: #385e63 !important; } ' +
       
        /* The dropdown caret */
        '.vanipedia-sidebar-menu .vector-menu-heading::after { ' +
        '  content: "▼"; ' +
        '  font-size: 0.75em; ' +
        '  color: #ffffff !important; ' +
        '  transition: transform 0.2s ease; ' +
        '} ' +
       
        /* Rotate the arrow when collapsed */
        '.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-heading::after { ' +
        '  transform: rotate(-90deg); ' +
        '} ' +
        /* The Expanded Content Area */
        '.vanipedia-sidebar-menu .vector-menu-content { ' +
        '  background-color: #eaeff0 !important; ' +   
        '  border: 1px solid #2c4c50 !important; ' +   
        '  border-top: none !important; ' +             
        '  border-radius: 0 0 4px 4px !important; ' +   
        '  padding: 0 0 4px 0 !important; ' +
        '  margin-bottom: 6px !important; ' +           
        '  box-shadow: 0 3px 6px rgba(0,0,0,0.15) !important; ' +
        '} ' +
        /* Reset the list styles */
        '.vanipedia-sidebar-menu .vector-menu-content ul { ' +
        '  list-style: none !important; ' +
        '  margin: 0 !important; ' +
        '  padding: 0 !important; ' +
        '} ' +
        /* Menu links */
        '.vanipedia-sidebar-menu .vector-menu-content li a { ' +
        '  display: block !important; ' +               
        '  padding: 8px 12px 8px 16px !important; ' +   
        '  color: #1b3033 !important; ' +               
        '  text-decoration: none !important; ' +       
        '  border-left: 3px solid transparent !important; ' +
        '  border-bottom: 1px solid rgba(44, 76, 80, 0.1) !important; ' +
        '  transition: all 0.15s ease !important; ' +
        '} ' +
       
        /* Remove separator from last item */
        '.vanipedia-sidebar-menu .vector-menu-content li:last-child a { ' +
        '  border-bottom: none !important; ' +
        '} ' +
        /* Hover effect for individual menu items */
        '.vanipedia-sidebar-menu .vector-menu-content li a:hover { ' +
        '  background-color: #dbe4e6 !important; ' +   
        '  border-left: 3px solid #2c4c50 !important; ' +
        '  color: #000000 !important; ' +               
        '} ';
       
    document.head.appendChild(style);
    // 3. Helper function to collapse all of our securely tagged menus
    function collapseAllSidebarMenus() {
        var menus = document.querySelectorAll('.vanipedia-sidebar-menu');
        for (var i = 0; i < menus.length; i++) {
            menus[i].classList.add('vanipedia-collapsed');
        }
    }
    // 4. Intercept clicks for True Accordion Behavior
    document.addEventListener('click', function(e) {
        var heading = e.target.closest('.vector-menu-heading');
        if (!heading) return;
        // ONLY react if this click belongs to one of our tagged sidebar menus
        var navContainer = heading.closest('.vanipedia-sidebar-menu');
        if (navContainer) {
            var isAlreadyOpen = !navContainer.classList.contains('vanipedia-collapsed');
           
            // Forcefully collapse ALL left sidebar menus first
            collapseAllSidebarMenus();
            // Open the clicked one if it wasn't already open
            if (!isAlreadyOpen) {
                navContainer.classList.remove('vanipedia-collapsed');
            }
           
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);
})();
mw.loader.using('mediawiki.user').then(function() {
    // Fetch the user's assigned groups
    var userGroups = mw.config.get('wgUserGroups') || [];
   
    // Define the roles that SHOULD see the administrative tabs
    var isAuthorized = userGroups.includes('sysop') ||
                      userGroups.includes('interface-admin') ||
                      userGroups.includes('Editor');
                     
    // If the user is not in one of those groups, restrict the UI
    if (!isAuthorized) {
        document.body.classList.add('restricted-ui');
    }
});
// Function to open accordion from TOC and jump there //
mw.hook('wikipage.content').add(function() {
   
    function handleAccordionTOC(hash) {
        if (!hash || hash === '#') return false;
       
        try {
            var targetId = hash.substring(1);
            var targetElement = document.getElementById(targetId) || document.getElementById(decodeURIComponent(targetId));
           
            if (!targetElement) return false;
            var $target = $(targetElement);
            // Match either h2 or h3
            var $heading = $target.is('h2, h3') ? $target : $target.closest('h2, h3');
           
            if (!$heading.length) return false;
            var $accordion = $heading.next('.book-accordion');
           
            // If the heading itself is not followed directly by an accordion,
            // check if the heading is located INSIDE a parent accordion (e.g., nested h3)
            var $parentAccordions = $heading.parents('.book-accordion');
            // If there's no direct accordion next to it and it's not inside any accordion,
            // let the browser handle standard jump.
            if (!$accordion.length && !$parentAccordions.length) return false;
            // 1. Open all ancestor accordions (if target heading is nested inside one)
            $parentAccordions.each(function() {
                var $parentCollapsible = $(this).children('.mw-collapsible');
                if ($parentCollapsible.length && $parentCollapsible.hasClass('mw-collapsed')) {
                    var pId = $parentCollapsible.attr('id');
                    if (pId) {
                        var pToggleClass = pId.replace('mw-customcollapsible-', 'mw-customtoggle-');
                        $('.' + pToggleClass).first().click();
                    }
                }
            });
            // 2. Open the accordion immediately following this heading (if present)
            if ($accordion.length) {
                var $collapsible = $accordion.children('.mw-collapsible, .mw-collapsible-content').first();
                if (!$collapsible.length) {
                    $collapsible = $accordion.find('.mw-collapsible');
                }
               
                if ($collapsible.length && $collapsible.hasClass('mw-collapsed')) {
                    var collapsibleId = $collapsible.attr('id');
                    if (collapsibleId) {
                        var toggleClass = collapsibleId.replace('mw-customcollapsible-', 'mw-customtoggle-');
                        $('.' + toggleClass).first().click();
                    }
                }
            }
            // Target element to scroll to (the accordion, or the heading itself if accordion is missing)
            var $scrollTarget = $accordion.length ? $accordion : $heading;
            // 3. Scroll to the visible section
            setTimeout(function() {
                $('html, body').animate({
                    scrollTop: $scrollTarget.offset().top - 75
                }, 300);
            }, 100);
            // Return true to indicate we successfully handled the TOC accordion click
            return true;
        } catch (e) {
            console.error("TOC Accordion script error:", e);
            return false;
        }
    }
    $(document).off('click.tocAccordion').on('click.tocAccordion', 'a[href^="#"]', function(e) {
        if ($(this).closest('#toc, .toc, .vector-toc').length > 0) {
            var href = $(this).attr('href');
           
            // Run our function to see if this is an accordion/heading link
            var isAccordion = handleAccordionTOC(href);
           
            // ONLY stop the default browser jump if we handled it
            if (isAccordion) {
                e.preventDefault();
               
                // Update the URL manually so the user can still share the link
                if (history.pushState) {
                    history.pushState(null, null, href);
                } else {
                    window.location.hash = href;
                }
            }
        }
    });
    if (window.location.hash) {
        setTimeout(function() {
            handleAccordionTOC(window.location.hash);
        }, 500);
    }
});
/* Sticky burger menu - Pure Floating Dropdown (Forces Unpinned & Full Title) */
(function () {
    let isMenuOpen = false;
    // 1. Force the sidebar out of pinned mode and return menu into the dropdown container
    function forceUnpinSidebar() {
        document.documentElement.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
        document.documentElement.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
        if (document.body) {
            document.body.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
            document.body.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
        }
        // Clear stored pin preference
        document.cookie = "vector-feature-main-menu-pinned=0; path=/; max-age=31536000";
        try { localStorage.setItem('vector-feature-main-menu-pinned', '0'); } catch(e) {}
        // If #vector-main-menu was moved into the pinned container, move it back into dropdown
        const mainMenu = document.getElementById('vector-main-menu');
        const unpinnedContainer = document.getElementById('vector-main-menu-unpinned-container') ||
                                  document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content');
        const pinnedContainer = document.getElementById('vector-main-menu-pinned-container');
        if (mainMenu && unpinnedContainer && pinnedContainer && pinnedContainer.contains(mainMenu)) {
            unpinnedContainer.appendChild(mainMenu);
        }
    }
    function getDropdownElement() {
        return document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content') ||
              document.getElementById('vector-main-menu-dropdown-content') ||
              document.getElementById('vector-main-menu-unpinned-container') ||
              document.getElementById('vector-main-menu');
    }
    function openMenu(menuBtn) {
        forceUnpinSidebar();
        const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
        const dropdown = getDropdownElement();
        if (!dropdown) return;
        if (checkbox) {
            checkbox.checked = true;
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
        }
        const rect = menuBtn.getBoundingClientRect();
        dropdown.style.setProperty('position', 'fixed', 'important');
        dropdown.style.setProperty('top', rect.bottom + 'px', 'important');
        dropdown.style.setProperty('left', rect.left + 'px', 'important');
        dropdown.style.setProperty('z-index', '99999', 'important');
        dropdown.style.setProperty('max-height', (window.innerHeight - rect.bottom - 16) + 'px', 'important');
        dropdown.style.setProperty('overflow-y', 'auto', 'important');
        dropdown.style.setProperty('display', 'block', 'important');
        dropdown.style.setProperty('visibility', 'visible', 'important');
        dropdown.style.setProperty('opacity', '1', 'important');
        dropdown.style.setProperty('background-color', 'var(--background-color-base, #ffffff)', 'important');
        dropdown.style.setProperty('box-shadow', '0 4px 16px rgba(0, 0, 0, 0.2)', 'important');
        dropdown.style.setProperty('border-radius', '4px', 'important');
        isMenuOpen = true;
    }
    function closeMenu() {
        const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
        const dropdown = getDropdownElement();
        if (!dropdown) return;
        if (checkbox) {
            checkbox.checked = false;
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
        }
        dropdown.style.removeProperty('position');
        dropdown.style.removeProperty('top');
        dropdown.style.removeProperty('left');
        dropdown.style.removeProperty('z-index');
        dropdown.style.removeProperty('max-height');
        dropdown.style.removeProperty('overflow-y');
        dropdown.style.removeProperty('background-color');
        dropdown.style.removeProperty('box-shadow');
        dropdown.style.removeProperty('border-radius');
        dropdown.style.setProperty('display', 'none', 'important');
        dropdown.style.setProperty('visibility', 'hidden', 'important');
        dropdown.style.setProperty('opacity', '0', 'important');
        isMenuOpen = false;
    }
    function initStickyBurger() {
        forceUnpinSidebar();
        const stickyHeader = document.getElementById('vector-sticky-header');
        if (!stickyHeader) return false;
        if (document.getElementById('vanipedia-sticky-main-menu')) return true;
        // 1. Create burger button
        const menuBtn = document.createElement('div');
        menuBtn.id = 'vanipedia-sticky-main-menu';
        menuBtn.title = 'Main menu';
        menuBtn.style.cursor = 'pointer';
        menuBtn.className = 'vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--icon-only';
        // 2. Add hamburger icon
        const iconSpan = document.createElement('span');
        iconSpan.className = 'vector-icon mw-ui-icon-menu mw-ui-icon-wikimedia-menu';
        menuBtn.appendChild(iconSpan);
        // 3. Toggle click handler (1st click opens, 2nd click rolls back)
        menuBtn.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();
            if (isMenuOpen) {
                closeMenu();
            } else {
                openMenu(menuBtn);
            }
        });
        // 4. Inject into start container
        let startContainer = stickyHeader.querySelector('.vector-sticky-header-start');
        if (!startContainer) {
            startContainer = document.createElement('div');
            startContainer.className = 'vector-sticky-header-start';
            stickyHeader.insertBefore(startContainer, stickyHeader.firstChild);
        }
       
        startContainer.insertBefore(menuBtn, startContainer.firstChild);
        return true;
    }
    // 5. Close when clicking anywhere outside
    document.addEventListener('click', function (e) {
        if (!isMenuOpen) return;
        const menuBtn = document.getElementById('vanipedia-sticky-main-menu');
        const dropdown = getDropdownElement();
        if (dropdown && menuBtn && !menuBtn.contains(e.target) && !dropdown.contains(e.target)) {
            closeMenu();
        }
    });
    // 6. Bootstrap initializer
    if (!initStickyBurger()) {
        const checkTimer = setInterval(function () {
            if (initStickyBurger()) {
                clearInterval(checkTimer);
            }
        }, 100);
        setTimeout(function () { clearInterval(checkTimer); }, 4000);
    }
    mw.hook('wikipage.content').add(initStickyBurger);
    $(initStickyBurger);
})();
/* Floating "Back to Top" Button Controller */
(function () {
    function initBackToTop() {
        if (document.getElementById('vanipedia-back-to-top')) return;
        // 1. Create button element with an up-arrow SVG icon
        const btn = document.createElement('button');
        btn.id = 'vanipedia-back-to-top';
        btn.title = 'Back to top';
        btn.setAttribute('aria-label', 'Back to top');
       
        btn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                <polyline points="18 15 12 9 6 15"></polyline>
            </svg>
        `;
        // 2. Smooth scroll to top on click
        btn.addEventListener('click', function (e) {
            e.preventDefault();
            window.scrollTo({ top: 0, behavior: 'smooth' });
        });
        // 3. Show button only after scrolling down 300px
        function toggleVisibility() {
            if (window.scrollY > 300) {
                btn.classList.add('visible');
            } else {
                btn.classList.remove('visible');
            }
        }
        window.addEventListener('scroll', toggleVisibility, { passive: true });
        toggleVisibility();
        document.body.appendChild(btn);
    }
    $(initBackToTop);
    mw.hook('wikipage.content').add(initBackToTop);
})();
(function () {
    if (!document.getElementById('vani-home')) {
        return;
    }
/* =====================================================
  ROTATING HERO PHOTOGRAPHS
  ===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const vaniHeroImages = [
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-01.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-02.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-03.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-04.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-05.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-06.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-07.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-08.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-09.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-10.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-11.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-12.jpg'
    ];
    const heroA = document.querySelector('.vh-photo-a');
    const heroB = document.querySelector('.vh-photo-b');
    if (heroA && heroB && vaniHeroImages.length) {
        let currentImage = -1;
        let activeLayer = heroA;
        function chooseHeroImage() {
            let next;
            do {
                next = Math.floor(
                    Math.random() * vaniHeroImages.length
                );
            } while (
                next === currentImage &&
                vaniHeroImages.length > 1
            );
            return next;
        }
        function changeHero(firstLoad) {
            const next = chooseHeroImage();
            const image = vaniHeroImages[next];
            if (firstLoad) {
                heroA.style.backgroundImage =
                    'url("' + image + '")';
                heroA.style.opacity = '1';
                heroB.style.opacity = '0';
                activeLayer = heroA;
                currentImage = next;
                return;
            }
            const incoming =
                activeLayer === heroA ? heroB : heroA;
            incoming.style.backgroundImage =
                'url("' + image + '")';
            incoming.style.opacity = '1';
            activeLayer.style.opacity = '0';
            activeLayer = incoming;
            currentImage = next;
        }
        changeHero(true);
        window.setInterval(function () {
            changeHero(false);
        }, 10000);
    }
}
/* =====================================================
  ROTATING ŚRĪLA PRABHUPĀDA SUBTITLE
  ===================================================== */
const vaniSubtitles = [
'His words still speak to us today.',
'His vāṇī brings closer to Kṛṣṇa.<br>His vision gives direction in lives.',
'Words spoken decades ago.<br>Wisdom that speaks personally today.',
'His vāṇī lives when we hear it and share with others.',
'There is always more to discover.<br>More to hear. More to understand. More to live.',
'His teachings give direction.<br>His example gives courage to follow.',
'From his heart —<br>a treasure of teachings for every generation.',
'His vāṇī. His compassion. His example.',
'His vāṇī lives on. His teachings guide us.<br>His mission continues.',
'His words cross decades and continents —<br>still reaching hearts, still changing lives.',
'His words continue to speak.<br>His teachings continue to guide.',
'He gave us a great treasure.<br>Vanipedia invites you to enter it.',
'A lifetime of teachings.<br>Invitation to hear him with an open heart.',
'His words illuminate the path.<br>His example inspires us to walk it.',
'He carried Kṛṣṇa consciousness across the world.<br>His vāṇī continues that journey today.',
'He lives on through his words,<br>his teachings and the mission he gave us.',
'His words continue to speak.<br>His teachings continue to guide.',
'Preserving and sharing Śrīla Prabhupāda’s vāṇī<br>so that generations may continue to hear from him.'
];
const vaniSubtitle =
    document.getElementById('vh-subtitle-rotation');
if (vaniSubtitle) {
    let currentSubtitle = -1;
    function chooseSubtitle() {
        let next;
        do {
            next = Math.floor(
                Math.random() * vaniSubtitles.length
            );
        } while (
            next === currentSubtitle &&
            vaniSubtitles.length > 1
        );
        return next;
    }
function showSubtitle(firstLoad) {
    const next = chooseSubtitle();
    if (firstLoad) {
        currentSubtitle = next;
        vaniSubtitle.innerHTML = vaniSubtitles[next];
        vaniSubtitle.classList.remove('is-changing');
        return;
    }
    // Fade out
    vaniSubtitle.classList.add('is-changing');
    window.setTimeout(function () {
        vaniSubtitle.innerHTML = vaniSubtitles[next];
        currentSubtitle = next;
        requestAnimationFrame(function () {
            requestAnimationFrame(function () {
                vaniSubtitle.classList.remove('is-changing');
            });
        });
    }, 450);
}
    /* Initial sentence */
    showSubtitle(true);
    /* Change every 10 seconds */
    window.setInterval(function () {
        showSubtitle(false);
    }, 12000);
}
/* ==========================================================
  MAIN PAGE — REMOVE NATIVE LINK TOOLTIPS
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    function removeMainPageLinkTitles() {
        document
            .querySelectorAll('#vani-home a[title]')
            .forEach(function (link) {
                link.removeAttribute('title');
            });
    }
    $(removeMainPageLinkTitles);
    mw.hook('wikipage.content').add(function () {
        removeMainPageLinkTitles();
    });
}
/* =====================================================
  RANDOM THEMATIC ARTICLE SPOTLIGHT
  ===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const discoverBox = document.getElementById('vh-random-article');
    if (discoverBox) {
        let articleLinks = [];
        let currentHref = null;
        const sourceUrl =
            mw.util.getUrl('Category:Vanipedia Gemini - Articles');
        function loadArticleLinks() {
            return fetch(sourceUrl)
                .then(function (response) {
                    return response.text();
                })
                .then(function (html) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');
                    const links = Array.from(
                        doc.querySelectorAll('#mw-content-text a')
                    );
                    articleLinks = links
                        .map(function (a) {
                            return {
                                title: a.textContent.trim(),
                                href: a.getAttribute('href')
                            };
                        })
                        .filter(function (item) {
                            if (!item.title || !item.href) {
                                return false;
                            }
                            if (!item.href.startsWith('/wiki/')) {
                                return false;
                            }
                            if (item.href.includes('Category:')) {
                                return false;
                            }
                            if (item.href.includes('Special:')) {
                                return false;
                            }
                            return true;
                        });
                    console.log(
                        'Spotlight links loaded:',
                        articleLinks.length
                    );
                });
        }
        function chooseRandomArticle() {
            if (!articleLinks.length) {
                return null;
            }
            let chosen;
            do {
                chosen =
                    articleLinks[
                        Math.floor(Math.random() * articleLinks.length)
                    ];
            } while (
                chosen.href === currentHref &&
                articleLinks.length > 1
            );
            return chosen;
        }
function getShortTitle(youtubeUrl) {
    const url =
        'https://www.youtube.com/oembed' +
        '?url=' +
        encodeURIComponent(youtubeUrl) +
        '&format=json';
    return fetch(url)
        .then(function(response) {
            if (!response.ok) {
                throw new Error(
                    'YouTube title HTTP ' +
                    response.status
                );
            }
            return response.json();
        })
        .then(function(data) {
            return (
                data &&
                data.title
                    ? data.title.trim()
                    : ''
            );
        })
        .catch(function(error) {
            console.warn(
                'Could not load Short title:',
                error
            );
            return '';
        });
}
function showRandomArticle() {
    const article = chooseRandomArticle();
    if (!article) {
        return;
    }
    currentHref = article.href;
    const title =
        decodeURIComponent(
            article.href
                .replace(/^\/wiki\//, '')
                .replace(/_/g, ' ')
        );
    const apiUrl =
        mw.util.wikiScript('api') +
        '?action=query' +
        '&format=json' +
        '&prop=extracts' +
        '&exintro=1' +
        '&explaintext=1' +
        '&exchars=260' +
        '&titles=' +
        encodeURIComponent(title);
    fetch(apiUrl)
        .then(function (response) {
            return response.json();
        })
        .then(function (data) {
            if (!data.query || !data.query.pages) {
                return;
            }
            const page = Object.values(data.query.pages)[0];
            /* Do not display pages without a real extract */
            if (
                !page ||
                !page.extract ||
                page.extract.trim().length < 40
            ) {
                showRandomArticle();
                return;
            }
            const extract = page.extract.trim();
            /* Fetch is finished. NOW begin the fade. */
            discoverBox.classList.add('is-changing');
            window.setTimeout(function () {
                discoverBox.innerHTML =
                    '<a class="vh-random-article-link" href="' +
                    article.href +
                    '">' +
                    '<div class="vh-random-article-title">' +
                    escapeHtml(article.title) +
                    '</div>' +
                    '<div class="vh-random-article-extract">' +
                    escapeHtml(extract) +
                    '</div>' +
                    '<div class="vh-random-article-more">' +
                    'Read article →' +
                    '</div>' +
                    '</a>';
                requestAnimationFrame(function () {
                    discoverBox.classList.remove('is-changing');
                });
            }, 500);
        })
        .catch(function () {
            /* Simply try another article */
            showRandomArticle();
        });
}
        function escapeHtml(text) {
            const div = document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
        loadArticleLinks()
            .then(function () {
                if (!articleLinks.length) {
                    return;
                }
                showRandomArticle();
                window.setInterval(function () {
                    showRandomArticle();
                }, 11000);
            })
            .catch(function (error) {
                console.error(
                    'Spotlight failed:',
                    error
                );
            });
    }
}
/* =====================================================
  FROM HIS LETTERS
  Random Śrīla Prabhupāda letter from Vanisource
  ===================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const letterBox =
        document.getElementById('vh-random-letter');
    if (letterBox) {
        const vanisourceApi =
            'https://vanisource.org/w/api.php';
        const rootCategory =
            'Category:Letters from Srila Prabhupada - by person';
        let recipientCategories = [];
        let currentLetter = '';
        let letterChanging = false;
        /* -------------------------------------------------
          LOAD RECIPIENT CATEGORIES
          ------------------------------------------------- */
        function loadLetterRecipients(cmcontinue) {
            let url =
                vanisourceApi +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(rootCategory) +
                '&cmtype=subcat' +
                '&cmlimit=500';
            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }
            return fetch(url)
                .then(function (response) {
                    return response.json();
                })
                .then(function (data) {
                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        data.query.categorymembers
                            .forEach(function (item) {
                                if (
                                    item.title &&
                                    item.title.endsWith(
                                        ' - Letters'
                                    )
                                ) {
                                    recipientCategories.push(
                                        item.title
                                    );
                                }
                            });
                    }
                    /* Continue if more than 500 */
                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadLetterRecipients(
                            data.continue.cmcontinue
                        );
                    }
                });
        }
        /* -------------------------------------------------
          CHOOSE RANDOM RECIPIENT
          ------------------------------------------------- */
        function chooseRecipient() {
            if (!recipientCategories.length) {
                return null;
            }
            return recipientCategories[
                Math.floor(
                    Math.random() *
                    recipientCategories.length
                )
            ];
        }
        /* -------------------------------------------------
          GET LETTERS FOR THAT RECIPIENT
          ------------------------------------------------- */
        function getRandomLetter(category) {
            const url =
                vanisourceApi +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(category) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=500';
            return fetch(url)
                .then(function (response) {
                    return response.json();
                })
                .then(function (data) {
                    if (
                        !data.query ||
                        !data.query.categorymembers ||
                        !data.query.categorymembers.length
                    ) {
                        return null;
                    }
                    const letters =
                        data.query.categorymembers;
                    let letter;
                    do {
                        letter =
                            letters[
                                Math.floor(
                                    Math.random() *
                                    letters.length
                                )
                            ];
                    } while (
                        currentLetter &&
                        letter.title === currentLetter &&
                        letters.length > 1
                    );
                    return letter;
                });
        }
        /* -------------------------------------------------
          RECIPIENT NAME
          ------------------------------------------------- */
        function getRecipientName(category) {
            return category
                .replace(/^Category:/, '')
                .replace(/ - Letters$/, '')
                .trim();
        }
        /* -------------------------------------------------
          TRY TO EXTRACT DATE FROM LETTER PAGE TITLE
          Supports dates at beginning such as:
          690101
          740516
          etc.
          ------------------------------------------------- */
        function getLetterDate(title) {
            const match =
                title.match(
                    /(?:^|\/)(\d{2})(\d{2})(\d{2})(?:\D|$)/
                );
            if (!match) {
                return '';
            }
            const yy =
                parseInt(match[1], 10);
            const month =
                parseInt(match[2], 10);
            const day =
                parseInt(match[3], 10);
            if (
                month < 1 ||
                month > 12 ||
                day < 1 ||
                day > 31
            ) {
                return '';
            }
            const year =
                1900 + yy;
            const months = [
                'January',
                'February',
                'March',
                'April',
                'May',
                'June',
                'July',
                'August',
                'September',
                'October',
                'November',
                'December'
            ];
            return (
                day +
                ' ' +
                months[month - 1] +
                ' ' +
                year
            );
        }
        /* -------------------------------------------------
          GET ACTUAL LETTER CONTENT
          ------------------------------------------------- */
        function getLetterPassage(letterTitle) {
            const url =
                vanisourceApi +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(letterTitle) +
                '&prop=text';
            return fetch(url)
                .then(function (response) {
                    return response.json();
                })
                .then(function (data) {
                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return '';
                    }
                    const wrapper =
                        document.createElement('div');
                    wrapper.innerHTML =
                        data.parse.text['*'];
                    /*
                    * Remove things which definitely are
                    * not part of the actual letter.
                    */
                    wrapper.querySelectorAll(
                        'table, .navbox, .toc, .mw-editsection, ' +
                        '.metadata, .noprint, style, script'
                    ).forEach(function (element) {
                        element.remove();
                    });
                    /*
                    * Collect useful paragraphs.
                    */
                    let paragraphs =
                        Array.from(
                            wrapper.querySelectorAll('p')
                        )
                        .map(function (p) {
                            return p.textContent
                                .replace(/\s+/g, ' ')
                                .trim();
                        })
.filter(function (text) {
    if (text.length < 120) {
        return false;
    }
    const lower = text.toLowerCase();
    /* Standard opening/closing formalities */
    if (
        lower.startsWith('dear ') ||
        lower.startsWith('my dear ') ||
        lower.includes('please accept my blessings') ||
        lower.includes('please accept my humble') ||
        lower.includes('i beg to acknowledge receipt') ||
        lower.includes('i am in due receipt') ||
        lower.includes('your ever well-wisher') ||
        lower.includes('hoping this will meet you') ||
        lower.includes('yours in the service') ||
        lower.includes('your servant')
    ) {
        return false;
    }
    /* Addresses / letter headers */
    if (
        lower.includes('iskcon') ||
        lower.includes('international society for krishna consciousness') ||
        lower.includes('founder-acarya') ||
        lower.includes('founder acharya') ||
        lower.includes('a.c. bhaktivedanta swami') ||
        lower.includes('tridandi goswami') ||
        lower.includes('tridandi gosvami') ||
        lower.includes('bhaktivedanta manor') ||
        lower.includes('hare krishna land')
    ) {
        return false;
    }
    /* Looks too much like an address */
    const addressWords = [
        'road',
        'street',
        'avenue',
        'building',
        'apartment',
        'bombay',
        'calcutta',
        'los angeles',
        'new york',
        'london'
    ];
    let addressHits = 0;
    addressWords.forEach(function (word) {
        if (lower.includes(word)) {
            addressHits++;
        }
    });
    if (addressHits >= 2) {
        return false;
    }
    /*
    * Require something resembling actual prose:
    * at least two sentence endings.
    */
    const sentences =
        (text.match(/[.!?](?:\s|$)/g) || []).length;
    if (sentences < 2) {
        return false;
    }
    return true;
});
                    if (!paragraphs.length) {
                        return '';
                    }
                    /*
                    * Prefer reasonably substantial passages.
                    */
                    let preferred =
                        paragraphs.filter(function (text) {
                            return (
text.length >= 180 &&
text.length <= 400
                            );
                        });
                    if (!preferred.length) {
                        preferred = paragraphs;
                    }
                    /*
                    * Random meaningful paragraph from
                    * within the letter.
                    */
                    let passage =
                        preferred[
                            Math.floor(
                                Math.random() *
                                preferred.length
                            )
                        ];
                    /*
                    * Prevent enormous passage in narrow
                    * left column.
                    */
if (passage.length > 300) {
    passage =
        passage.substring(0, 300);
    const lastSpace =
        passage.lastIndexOf(' ');
    if (lastSpace > 250) {
        passage =
            passage.substring(
                0,
                lastSpace
            );
    }
    passage += '…';
}
                    return passage;
                });
        }
        /* -------------------------------------------------
          SAFE HTML
          ------------------------------------------------- */
        function escapeLetterHtml(text) {
            const div =
                document.createElement('div');
            div.textContent = text;
            return div.innerHTML;
        }
        /* -------------------------------------------------
          BUILD VANISOURCE URL
          ------------------------------------------------- */
        function getLetterUrl(title) {
            return (
                'https://vanisource.org/wiki/' +
                encodeURIComponent(
                    title.replace(/ /g, '_')
                )
                    .replace(/%2F/g, '/')
                    .replace(/%3A/g, ':')
            );
        }
        /* -------------------------------------------------
          FIND A VALID RANDOM LETTER
          ------------------------------------------------- */
        function prepareRandomLetter(attempt) {
            attempt = attempt || 0;
            /*
            * Prevent an endless retry loop if Vanisource
            * unexpectedly returns unusable pages.
            */
            if (attempt > 15) {
                return Promise.resolve(null);
            }
            const category =
                chooseRecipient();
            if (!category) {
                return Promise.resolve(null);
            }
            return getRandomLetter(category)
                .then(function (letter) {
                    if (!letter || !letter.title) {
                        return prepareRandomLetter(
                            attempt + 1
                        );
                    }
                    return getLetterPassage(
                        letter.title
                    )
                        .then(function (passage) {
                            if (
                                !passage ||
                                passage.length < 80
                            ) {
                                return prepareRandomLetter(
                                    attempt + 1
                                );
                            }
                            return {
                                title:
                                    letter.title,
                                recipient:
                                    getRecipientName(
                                        category
                                    ),
                                date:
                                    getLetterDate(
                                        letter.title
                                    ),
                                passage:
                                    passage,
                                href:
                                    getLetterUrl(
                                        letter.title
                                    )
                            };
                        });
                });
        }
        /* -------------------------------------------------
          DISPLAY LETTER
          ------------------------------------------------- */
        function showRandomLetter() {
            if (letterChanging) {
                return;
            }
            letterChanging = true;
            /*
            * Prepare the next letter while the current
            * one remains fully visible.
            */
            prepareRandomLetter(0)
                .then(function (letter) {
                    if (!letter) {
                        letterChanging = false;
                        return;
                    }
                    currentLetter =
                        letter.title;
                    /*
                    * Only start fading AFTER the new
                    * content is ready.
                    */
                    letterBox.classList.add(
                        'is-changing'
                    );
                    window.setTimeout(
                        function () {
                            let meta = '';
                            if (letter.date) {
                                meta =
                                    '<div class="vh-random-letter-date">' +
                                    escapeLetterHtml(
                                        letter.date
                                    ) +
                                    '</div>';
                            }
                            letterBox.innerHTML =
                                '<a class="vh-random-letter-link" ' +
                                'href="' +
                                letter.href +
                                '">' +
'<div class="vh-random-letter-person">' +
'To ' +
escapeLetterHtml(
    letter.recipient
) +
'</div>' +
                                meta +
                                '<div class="vh-random-letter-text">' +
                                escapeLetterHtml(
                                    letter.passage
                                ) +
                                '</div>' +
                                '<div class="vh-random-letter-more">' +
                                'Read letter →' +
                                '</div>' +
                                '</a>';
                            requestAnimationFrame(
                                function () {
                                    letterBox.classList.remove(
                                        'is-changing'
                                    );
                                    letterChanging = false;
                                }
                            );
                        },
                        450
                    );
                })
                .catch(function (error) {
                    console.error(
                        'Letter feature failed:',
                        error
                    );
                    letterChanging = false;
                });
        }
        /* -------------------------------------------------
          START
          ------------------------------------------------- */
        loadLetterRecipients()
            .then(function () {
                if (!recipientCategories.length) {
                    console.warn(
                        'No Vanisource letter recipients found.'
                    );
                    return;
                }
                console.log(
                    'Letter recipient categories loaded:',
                    recipientCategories.length
                );
                /*
                * First letter immediately.
                */
                showRandomLetter();
                /*
                * New letter every 10 seconds.
                */
                window.setInterval(
                    showRandomLetter,
                    10000
                );
            })
            .catch(function (error) {
                console.error(
                    'Could not load Vanisource letters:',
                    error
                );
            });
    }
}
/* ==========================================================
  VIRTUAL CONTINUOUS ŚRĪLA PRABHUPĀDA TIMELINE
  Includes 1896–1964 + 1965–1977
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const timelineWindow =
        document.querySelector('.vh-lifeline-window');
    const timelineTrack =
        document.getElementById('vh-timeline-track');
const yearButtons =
    document.querySelectorAll(
        '#vh-timeline-years .vh-year'
    );
    if (timelineWindow && timelineTrack) {
        const API =
            'https://vanipedia.org/w/api.php';
        const ITEM_WIDTH = 220;
        const AUTO_SPEED = 52;
        const VISIBLE_ITEMS = 22;
        let events = [];
        let nodes = [];
        let firstIndex = 0;
        let offsetX = 0;
        let dragging = false;
        let dragMoved = false;
        let pointerStartX = 0;
        let previousPointerX = 0;
        let lastFrame =
            performance.now();
        let animationId = null;
        /* ==================================================
          TEXT HELPERS
          ================================================== */
        function cleanText(text) {
            return text
                .replace(/\s+/g, ' ')
                .replace(/^[\s\-–—:,]+/, '')
                .trim();
        }
        function shortenText(text) {
            text =
                cleanText(text);
            if (text.length <= 95) {
                return text;
            }
            let result =
                text.substring(0, 95);
            const space =
                result.lastIndexOf(' ');
            if (space > 70) {
                result =
                    result.substring(
                        0,
                        space
                    );
            }
            return result + '…';
        }
        /* ==================================================
          DEFAULT PAGE LINK
          ================================================== */
        function makePageUrl(page) {
            return (
                'https://vanipedia.org/wiki/' +
                encodeURIComponent(
                    page.replace(/ /g, '_')
                )
                .replace(/%2F/g, '/')
                .replace(/%3A/g, ':')
            );
        }
        /* ==================================================
          FIND BEST LINK INSIDE ENTRY
          ================================================== */
        function getUsefulLink(
            element,
            fallback
        ) {
            const usefulLink =
                Array
                    .from(
                        element.querySelectorAll('a')
                    )
                    .find(
                        function (a) {
                            const h =
                                a.getAttribute(
                                    'href'
                                );
                            return (
                                h &&
                                !h.includes(
                                    'action=edit'
                                ) &&
                                !h.includes(
                                    'redlink=1'
                                )
                            );
                        }
                    );
            if (!usefulLink) {
                return fallback;
            }
            const rawHref =
                usefulLink.getAttribute(
                    'href'
                );
            if (!rawHref) {
                return fallback;
            }
            try {
                return new URL(
                    rawHref,
                    'https://vanipedia.org'
                ).href;
            } catch (e) {
                return fallback;
            }
        }
        /* ==================================================
          FETCH 1896–1964
          Main timeline page
          ================================================== */
        function fetchEarlyTimeline() {
            const page =
                "Timeline of Srila Prabhupada's Life";
            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page) +
                '&prop=text';
            return fetch(url)
                .then(function (response) {
                    return response.json();
                })
                .then(function (data) {
                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return [];
                    }
                    const temp =
                        document.createElement('div');
                    temp.innerHTML =
                        data.parse.text['*'];
                    const result = [];
                    temp
                        .querySelectorAll('li')
                        .forEach(function (li) {
                            const fullText =
                                cleanText(
                                    li.textContent
                                );
                            /*
                            * Expected examples:
                            *
                            * 1896 - September 01 - Calcutta - ...
                            * 1922 - Meets ...
                            * 1944 - February 14 - Publishes ...
                            * 1959 - September 17 - ...
                            *
                            * We only keep 1896–1964.
                            */
                            const match =
                                fullText.match(
                                    /^((?:18|19)\d{2})\s*[-–—]\s*(.+)$/i
                                );
                            if (!match) {
                                return;
                            }
                            const year =
                                parseInt(
                                    match[1],
                                    10
                                );
                            if (
                                year < 1896 ||
                                year > 1964
                            ) {
                                return;
                            }
                            let remainder =
                                cleanText(
                                    match[2]
                                );
                            if (!remainder) {
                                return;
                            }
                            /*
                            * Try to extract a date
                            * from the remainder.
                            */
                            let displayDate =
                                String(year);
                            let description =
                                remainder;
                            const monthMatch =
                                remainder.match(
                                    /^(January|February|March|April|May|June|July|August|September|October|November|December)(?:\s+(\d{1,2}))?\s*[-–—]\s*(.+)$/i
                                );
                            if (monthMatch) {
                                const month =
                                    monthMatch[1];
                                const day =
                                    monthMatch[2]
                                        ? monthMatch[2]
                                        : '';
                                description =
                                    cleanText(
                                        monthMatch[3]
                                    );
                                displayDate =
                                    month;
                                if (day) {
                                    displayDate +=
                                        ' ' +
                                        day;
                                }
                                displayDate +=
                                    ', ' +
                                    year;
                            } else {
                                /*
                                * If no month is present,
                                * show year only.
                                */
                                description =
                                    remainder;
                            }
                            description =
                                shortenText(
                                    description
                                );
                            if (!description) {
                                return;
                            }
                            const fallback =
                                makePageUrl(page);
                            const href =
                                getUsefulLink(
                                    li,
                                    fallback
                                );
                            result.push({
                                year:
                                    year,
                                group:
                                    '1896-1964',
                                date:
                                    displayDate,
                                text:
                                    description,
                                href:
                                    href
                            });
                        });
                    return result;
                });
        }
        /* ==================================================
          FETCH ONE YEAR: 1965–1977
          ================================================== */
        function fetchYear(year) {
            const page =
                "Timeline of Srila Prabhupada's Life - " +
                year;
            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page) +
                '&prop=text';
            return fetch(url)
                .then(function (response) {
                    return response.json();
                })
                .then(function (data) {
                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return [];
                    }
                    const temp =
                        document.createElement('div');
                    temp.innerHTML =
                        data.parse.text['*'];
                    const result = [];
                    temp
                        .querySelectorAll('li')
                        .forEach(function (li) {
                            const fullText =
                                cleanText(
                                    li.textContent
                                );
                            const match =
                                fullText.match(
                                    /^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})(?:,?\s+([0-9]{1,2}(?::[0-9]{2})?(?:\s*[AP]\.?M\.?)?))?\s*[-–—]\s*(.+)$/i
                                );
                            if (!match) {
                                return;
                            }
                            const month =
                                match[1];
                            const day =
                                match[2];
                            const time =
                                match[3]
                                    ? match[3].trim()
                                    : '';
                            const description =
                                shortenText(
                                    match[4]
                                );
                            if (!description) {
                                return;
                            }
                            const fallback =
                                makePageUrl(page);
                            const href =
                                getUsefulLink(
                                    li,
                                    fallback
                                );
                            let date =
                                month +
                                ' ' +
                                day +
                                ', ' +
                                year;
                            if (time) {
                                date +=
                                    ' · ' +
                                    time;
                            }
                            result.push({
                                year:
                                    year,
                                group:
                                    String(year),
                                date:
                                    date,
                                text:
                                    description,
                                href:
                                    href
                            });
                        });
                    return result;
                });
        }
        /* ==================================================
          CREATE NODE
          ================================================== */
        function createNode() {
            const link =
                document.createElement('a');
            link.className =
                'vh-timeline-item';
            link.draggable =
                false;
            const date =
                document.createElement('div');
            date.className =
                'vh-timeline-date';
            const dot =
                document.createElement('span');
            dot.className =
                'vh-timeline-dot';
            const text =
                document.createElement('div');
            text.className =
                'vh-timeline-text';
            link.appendChild(date);
            link.appendChild(dot);
            link.appendChild(text);
            link.addEventListener(
                'click',
                function (event) {
                    if (dragMoved) {
                        event.preventDefault();
                    }
                }
            );
            return link;
        }
        /* ==================================================
          POPULATE NODE
          ================================================== */
        function populateNode(
            node,
            eventIndex
        ) {
            const total =
                events.length;
            eventIndex =
                (
                    (
                        eventIndex %
                        total
                    ) +
                    total
                ) %
                total;
            const item =
                events[eventIndex];
            node.href =
                item.href;
            node.dataset.year =
                item.year;
            node.dataset.group =
                item.group;
            node
                .querySelector(
                    '.vh-timeline-date'
                )
                .textContent =
                    item.date;
            node
                .querySelector(
                    '.vh-timeline-text'
                )
                .textContent =
                    item.text;
        }
        /* ==================================================
          BUILD VIRTUAL STRIP
          ================================================== */
        function buildVirtualTimeline() {
            timelineTrack.innerHTML = '';
            nodes = [];
            const needed =
                Math.max(
                    VISIBLE_ITEMS,
                    Math.ceil(
                        timelineWindow.clientWidth /
                        ITEM_WIDTH
                    ) + 6
                );
            for (
                let i = 0;
                i < needed;
                i++
            ) {
                const node =
                    createNode();
                populateNode(
                    node,
                    firstIndex + i
                );
                timelineTrack.appendChild(
                    node
                );
                nodes.push(
                    node
                );
            }
            timelineTrack.style.width =
                (
                    needed *
                    ITEM_WIDTH
                ) +
                'px';
            offsetX =
                -ITEM_WIDTH;
            firstIndex =
                (
                    firstIndex -
                    1 +
                    events.length
                ) %
                events.length;
            refreshAllNodes();
            draw();
        }
        function refreshAllNodes() {
            nodes.forEach(
                function (
                    node,
                    position
                ) {
                    populateNode(
                        node,
                        firstIndex +
                        position
                    );
                }
            );
        }
        /* ==================================================
          RECYCLE LEFT
          ================================================== */
        function recycleLeft() {
            const node =
                nodes.shift();
            nodes.push(
                node
            );
            timelineTrack.appendChild(
                node
            );
            firstIndex =
                (
                    firstIndex +
                    1
                ) %
                events.length;
            populateNode(
                node,
                firstIndex +
                nodes.length -
                1
            );
            offsetX +=
                ITEM_WIDTH;
        }
        /* ==================================================
          RECYCLE RIGHT
          ================================================== */
        function recycleRight() {
            const node =
                nodes.pop();
            firstIndex =
                (
                    firstIndex -
                    1 +
                    events.length
                ) %
                events.length;
            nodes.unshift(
                node
            );
            timelineTrack.insertBefore(
                node,
                timelineTrack.firstChild
            );
            populateNode(
                node,
                firstIndex
            );
            offsetX -=
                ITEM_WIDTH;
        }
        function normalizePosition() {
            while (
                offsetX <=
                -ITEM_WIDTH * 2
            ) {
                recycleLeft();
            }
            while (
                offsetX > 0
            ) {
                recycleRight();
            }
        }
        /* ==================================================
          DRAW
          ================================================== */
        function draw() {
            timelineTrack.style.transform =
                'translate3d(' +
                offsetX +
                'px,0,0)';
        }
        /* ==================================================
          ACTIVE YEAR BUTTON
          ================================================== */
function setActiveButton(group) {
    yearButtons.forEach(
        function (button) {
            const value =
                button.classList.contains(
                    'vh-year-early'
                )
                    ? '1896-1964'
                    : button.textContent.trim();
            button.classList.toggle(
                'is-active',
                value === group
            );
        }
    );
}
        /* ==================================================
          JUMP TO YEAR / PERIOD
          ================================================== */
        function jumpToGroup(group) {
            if (!events.length) {
                return;
            }
            let targetIndex;
            if (group === '1896-1964') {
                targetIndex =
                    events.findIndex(
                        function (item) {
                            return (
                                item.group ===
                                '1896-1964'
                            );
                        }
                    );
            } else {
                const year =
                    parseInt(
                        group,
                        10
                    );
                targetIndex =
                    events.findIndex(
                        function (item) {
                            return (
                                item.year ===
                                year
                            );
                        }
                    );
            }
            if (targetIndex === -1) {
                return;
            }
            /*
            * Put target near left side,
            * with one virtual buffer item before it.
            */
            firstIndex =
                (
                    targetIndex -
                    1 +
                    events.length
                ) %
                events.length;
            offsetX =
                -ITEM_WIDTH;
            refreshAllNodes();
            draw();
            setActiveButton(
                group
            );
        }
/* ==================================================
  YEAR NAVIGATION
  ================================================== */
yearButtons.forEach(
    function (button) {
        button.addEventListener(
            'click',
            function () {
                const value =
                    button.classList.contains(
                        'vh-year-early'
                    )
                        ? '1896-1964'
                        : button.textContent.trim();
                jumpToGroup(value);
            }
        );
    }
);
        /* ==================================================
          ANIMATION
          ================================================== */
        function animate(now) {
            let elapsed =
                (
                    now -
                    lastFrame
                ) /
                1000;
            lastFrame =
                now;
            if (elapsed > 0.05) {
                elapsed =
                    0.05;
            }
            if (!dragging) {
                offsetX -=
                    AUTO_SPEED *
                    elapsed;
                normalizePosition();
                draw();
            }
            animationId =
                requestAnimationFrame(
                    animate
                );
        }
        /* ==================================================
          DRAGGING
          ================================================== */
        timelineWindow.addEventListener(
            'pointerdown',
            function (event) {
                if (
                    event.pointerType ===
                        'mouse' &&
                    event.button !== 0
                ) {
                    return;
                }
                dragging =
                    true;
                dragMoved =
                    false;
                pointerStartX =
                    event.clientX;
                previousPointerX =
                    event.clientX;
                timelineWindow.classList.add(
                    'is-dragging'
                );
                timelineWindow.setPointerCapture(
                    event.pointerId
                );
                event.preventDefault();
            }
        );
        timelineWindow.addEventListener(
            'pointermove',
            function (event) {
                if (!dragging) {
                    return;
                }
                const currentX =
                    event.clientX;
                const difference =
                    currentX -
                    previousPointerX;
                previousPointerX =
                    currentX;
                if (
                    Math.abs(
                        currentX -
                        pointerStartX
                    ) > 4
                ) {
                    dragMoved =
                        true;
                }
                offsetX +=
                    difference;
                normalizePosition();
                draw();
                event.preventDefault();
            }
        );
        function endDrag(event) {
            if (!dragging) {
                return;
            }
            dragging =
                false;
            timelineWindow.classList.remove(
                'is-dragging'
            );
            try {
                if (
                    timelineWindow.hasPointerCapture(
                        event.pointerId
                    )
                ) {
                    timelineWindow.releasePointerCapture(
                        event.pointerId
                    );
                }
            } catch (e) {}
            if (dragMoved) {
                window.setTimeout(
                    function () {
                        dragMoved =
                            false;
                    },
                    180
                );
            }
        }
        timelineWindow.addEventListener(
            'pointerup',
            endDrag
        );
        timelineWindow.addEventListener(
            'pointercancel',
            endDrag
        );
        timelineWindow.addEventListener(
            'dragstart',
            function (event) {
                event.preventDefault();
            }
        );
        /* ==================================================
          RESIZE
          ================================================== */
        let resizeTimer;
        window.addEventListener(
            'resize',
            function () {
                clearTimeout(
                    resizeTimer
                );
                resizeTimer =
                    setTimeout(
                        function () {
                            if (
                                events.length
                            ) {
                                buildVirtualTimeline();
                            }
                        },
                        150
                    );
            }
        );
        /* ==================================================
          LOAD ALL TIMELINE DATA
          ================================================== */
        const requests = [];
        /*
        * First: 1896–1964
        */
        requests.push(
            fetchEarlyTimeline()
        );
        /*
        * Then: 1965–1977
        */
        for (
            let year = 1965;
            year <= 1977;
            year++
        ) {
            requests.push(
                fetchYear(year)
            );
        }
        Promise
            .all(requests)
            .then(
                function (results) {
                    events =
                        results.flat();
                    /*
                    * Ensure chronological order
                    * between groups.
                    */
                    events.sort(
                        function (a, b) {
                            return (
                                a.year -
                                b.year
                            );
                        }
                    );
                    console.log(
                        'TIMELINE EVENTS:',
                        events.length
                    );
                    console.log(
                        'EARLY EVENTS:',
                        events.filter(
                            function (item) {
                                return (
                                    item.group ===
                                    '1896-1964'
                                );
                            }
                        ).length
                    );
                    if (!events.length) {
                        console.warn(
                            'No timeline events found.'
                        );
                        return;
                    }
                    /*
                    * Start with 1965.
                    */
                    const startIndex =
                        events.findIndex(
                            function (item) {
                                return (
                                    item.year ===
                                    1965
                                );
                            }
                        );
                    firstIndex =
                        startIndex >= 0
                            ? startIndex
                            : 0;
                    buildVirtualTimeline();
                    setActiveButton(
                        '1965'
                    );
                    lastFrame =
                        performance.now();
                    animationId =
                        requestAnimationFrame(
                            animate
                        );
                }
            )
            .catch(
                function (error) {
                    console.error(
                        'Timeline loading failed:',
                        error
                    );
                }
            );
    }
}
/* =========================================================
  RANDOM NECTAR DROP — HOMEPAGE QUOTE
  ========================================================= */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const quoteWrap =
        document.getElementById('vh-random-quote-wrap');
    const quoteText =
        document.getElementById('vh-random-quote');
    const quoteSource =
        document.getElementById('vh-random-quote-source');
    if (quoteWrap && quoteText && quoteSource) {
        const API =
            'https://vanipedia.org/w/api.php';
        const CATEGORY =
            'Category:Nectar Drops from Srila Prabhupada';
        let nectarPages = [];
        let quoteChanging = false;
        let currentPage = '';
        /* -----------------------------------------
          LOAD ALL NECTAR DROP PAGES
          ----------------------------------------- */
        function loadNectarPages(cmcontinue) {
            let url =
                API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(CATEGORY) +
                '&cmnamespace=0' +
                '&cmlimit=max';
            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }
            return fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'HTTP ' + response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        nectarPages =
                            nectarPages.concat(
                                data.query.categorymembers
                            );
                    }
                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadNectarPages(
                            data.continue.cmcontinue
                        );
                    }
                    console.log(
                        'Nectar Drop pages loaded:',
                        nectarPages.length
                    );
                });
        }
        /* -----------------------------------------
          GET RANDOM NECTAR DROP
          ----------------------------------------- */
        function getRandomNectarDrop() {
            if (
                quoteChanging ||
                !nectarPages.length
            ) {
                return;
            }
            quoteChanging = true;
            let page;
            for (
                let attempt = 0;
                attempt < 20;
                attempt++
            ) {
                const candidate =
                    nectarPages[
                        Math.floor(
                            Math.random() *
                            nectarPages.length
                        )
                    ];
                if (
                    candidate &&
                    candidate.title !== currentPage
                ) {
                    page = candidate;
                    break;
                }
            }
            if (!page) {
                quoteChanging = false;
                return;
            }
            /* -------------------------------------
              GET RAW WIKITEXT
              ------------------------------------- */
            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page.title) +
                '&prop=wikitext';
            fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'HTTP ' +
                            response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        throw new Error(
                            'No wikitext returned.'
                        );
                    }
                    const wikitext =
                        data.parse.wikitext['*'];
                    /* ---------------------------------
                      FIND AUDIOBOX TEMPLATE
                      --------------------------------- */
                    const start =
                        wikitext.indexOf(
                            '{{Audiobox_NDrops|'
                        );
                    if (start === -1) {
                        quoteChanging = false;
                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );
                        return;
                    }
                    const end =
                        wikitext.indexOf(
                            '}}',
                            start
                        );
                    if (end === -1) {
                        quoteChanging = false;
                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );
                        return;
                    }
                    const template =
                        wikitext.substring(
                            start +
                            '{{Audiobox_NDrops|'.length,
                            end
                        );
                    /* ---------------------------------
                      SPLIT TEMPLATE PARAMETERS
                      --------------------------------- */
                    const parts =
                        template.split('|');
                    if (parts.length < 5) {
                        quoteChanging = false;
                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );
                        return;
                    }
                    /*
                    * Parameter structure:
                    *
                    * 0 = Nectar Drops from...
                    * 1 = mp3 player
                    * 2 = QUOTE
                    * 3 = Vanisource page
                    * 4 = source label
                    */
                    let quote =
                        parts[2]
                            .trim()
                            .replace(
                                /^["“]+/,
                                ''
                            )
                            .replace(
                                /["”]+$/,
                                ''
                            )
                            .trim();
const vanisourcePage =
    parts[3]
        .trim()
        /* remove wiki-link brackets */
        .replace(/^\[\[/, '')
        .replace(/\]\]$/, '')
        /* if link contains |display text, keep only target */
        .split('|')[0]
        /* remove namespace prefix */
        .replace(/^Vanisource:/i, '')
        /* remove any remaining stray brackets/braces */
        .replace(/[\[\]\{\}]/g, '')
        .trim();
                    const sourceLabel =
                        parts[4]
                            .trim();
                    if (!quote) {
                        quoteChanging = false;
                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );
                        return;
                    }
                    /* ---------------------------------
                      CLEAN SIMPLE WIKITEXT
                      --------------------------------- */
                    quote =
                        quote
                            .replace(
                                /'''?/g,
                                ''
                            )
                            .replace(
                                /\[\[([^|\]]+)\|([^\]]+)\]\]/g,
                                '$2'
                            )
                            .replace(
                                /\[\[([^\]]+)\]\]/g,
                                '$1'
                            )
                            .replace(
                                /\s+/g,
                                ' '
                            )
                            .trim();
                    /* ---------------------------------
                      SOURCE LINK
                      --------------------------------- */
const href =
    'https://vanisource.org/wiki/' +
    vanisourcePage
        .replace(/ /g, '_')
        .split('/')
        .map(encodeURIComponent)
        .join('/');
/* ---------------------------------
  DISPLAY
  --------------------------------- */
currentPage = page.title;
quoteText.classList.add(
    'is-changing'
);
quoteSource.classList.add(
    'is-changing'
);
window.setTimeout(
    function() {
const cleanQuote =
    quote
        .replace(
            /\s*\(?\[\[Vanisource:.*$/i,
            ''
        )
        .trim();
quoteText.textContent =
    cleanQuote;
        quoteSource.innerHTML =
            '';
        const link =
            document.createElement('a');
        link.href =
            href;
let cleanSource = page.title
    .replace(/^\d{6}[a-z]?\s*-\s*/i, '')
    .replace(
        /\s*-\s*Srila Prabhupada Speaks a Nectar Drop in\s*/i,
        ', '
    )
    .trim();
link.textContent =
    cleanSource;
        quoteSource.appendChild(
            link
        );
        /*
        * Reveal only after the first
        * random quote is ready.
        */
        quoteWrap.style.visibility =
            'visible';
        requestAnimationFrame(
            function() {
                quoteText.classList.remove(
                    'is-changing'
                );
                quoteSource.classList.remove(
                    'is-changing'
                );
                quoteChanging = false;
            }
        );
    },
    450
);
                })
                .catch(function(error) {
                    console.error(
                        'Random Nectar Drop failed:',
                        error
                    );
                    quoteChanging = false;
                });
        }
        /* -----------------------------------------
          START
          ----------------------------------------- */
        loadNectarPages()
            .then(function() {
                if (nectarPages.length) {
                    getRandomNectarDrop();
                    window.setInterval(
                        getRandomNectarDrop,
                        9000
                    );
                }
            })
            .catch(function(error) {
                console.error(
                    'Nectar Drop category failed:',
                    error
                );
            });
    }
}
/* Vani Treasure (Featured) */
/* ==========================================================
  HOMEPAGE — RANDOM SHORT
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const shortCard =
        document.getElementById('vh-random-short');
    if (shortCard) {
        const API =
            'https://vanipedia.org/w/api.php';
        const SHORTS_PAGE =
            'VanimediaMayapur YouTube SHORTS List';
        let shortItems = [];
        let currentShort = -1;
        /* --------------------------------------------------
          LOAD SHORTS LIST
          -------------------------------------------------- */
        function loadShorts() {
            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(SHORTS_PAGE) +
                '&prop=wikitext';
            fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        throw new Error(
                            'Shorts wikitext not found'
                        );
                    }
                    const wiki =
                        data.parse.wikitext['*'];
                    /*
                    * Example:
                    *
                    * File:Short_1139.png
                    * |1139 - 21 August
                    * |link=https://www.youtube.com/shorts/_Wumpw8SAYs
                    */
                    const regex =
                        /File:(Short_(\d+)\.(?:png|jpg|jpeg|webp))\s*\|\s*(\d+)\s*-\s*([^|\n]+?)\s*\|\s*link=(https:\/\/www\.youtube\.com\/shorts\/[^\s|\n]+)/gi;
                    let match;
                    while (
                        (match = regex.exec(wiki)) !== null
                    ) {
                        shortItems.push({
                            file: match[1],
                            number: match[3],
                            date: match[4].trim(),
                            youtube: match[5].trim()
                        });
                    }
                    console.log(
                        'Homepage Shorts loaded:',
                        shortItems.length
                    );
                    if (shortItems.length) {
                        showRandomShort();
                    }
                })
                .catch(function(error) {
                    console.error(
                        'Homepage Shorts failed:',
                        error
                    );
                });
        }
        /* --------------------------------------------------
          GET ACTUAL THUMBNAIL URL
          -------------------------------------------------- */
        function getShortThumbnail(fileName) {
            const url =
                API +
                '?action=query' +
                '&format=json' +
                '&prop=imageinfo' +
                '&iiprop=url' +
                '&iiurlwidth=400' +
                '&titles=' +
                encodeURIComponent(
                    'File:' + fileName
                );
            return fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.query ||
                        !data.query.pages
                    ) {
                        return '';
                    }
                    const pages =
                        Object.values(
                            data.query.pages
                        );
                    if (
                        !pages.length ||
                        !pages[0].imageinfo ||
                        !pages[0].imageinfo.length
                    ) {
                        return '';
                    }
                    const info =
                        pages[0].imageinfo[0];
                    return (
                        info.thumburl ||
                        info.url ||
                        ''
                    );
                });
        }
        /* --------------------------------------------------
          DISPLAY RANDOM SHORT
          -------------------------------------------------- */
        function showRandomShort() {
            if (!shortItems.length) {
                return;
            }
            let index;
            do {
                index =
                    Math.floor(
                        Math.random() *
                        shortItems.length
                    );
            }
            while (
                shortItems.length > 1 &&
                index === currentShort
            );
            currentShort = index;
            const item =
                shortItems[index];
            const media =
                shortCard.querySelector(
                    '.vh-feature-media'
                );
            const copy =
                shortCard.querySelector(
                    '.vh-feature-copy'
                );
            if (!media || !copy) {
                return;
            }
            /* ---------- thumbnail ---------- */
getShortThumbnail(item.file)
    .then(function(imageUrl) {
        if (!imageUrl) {
            return;
        }
        const preload = new Image();
        preload.onload = function() {
            media.style.backgroundImage =
                'url("' + imageUrl + '")';
        };
        preload.src = imageUrl;
    });
            /* ---------- text ---------- */
            copy.innerHTML = '';
            const type =
                document.createElement('small');
            type.textContent =
                'SHORT';
const title =
    document.createElement('strong');
title.textContent =
    'Loading title…';
            copy.appendChild(type);
            copy.appendChild(title);
getShortTitle(item.youtube)
    .then(function(actualTitle) {
        /*
        * Make sure this card has not already
        * rotated to another Short.
        */
        if (
            shortItems[currentShort] !== item
        ) {
            return;
        }
let cleanTitle = actualTitle
    .replace(/\s*-\s*Prabhupada.*$/i, '')
    .trim();
title.textContent =
    cleanTitle ||
    item.number;
    });
            /* ---------- click ---------- */
            shortCard.onclick =
                function() {
                    window.open(
                        item.youtube,
                        '_blank',
                        'noopener'
                    );
                };
        }
        /* --------------------------------------------------
          START
          -------------------------------------------------- */
        loadShorts();
        /*
        * Temporary individual rotation.
        *
        * Later we'll replace this with the common
        * staggered controller for all four cards.
        */
        window.setInterval(
            function() {
                if (shortItems.length) {
                    showRandomShort();
                }
            },
            10500
        );
    }
}
/* ==========================================================
  HOMEPAGE — RANDOM VIDEO
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const videoCard =
        document.getElementById('vh-random-video');
    if (videoCard) {
        const API =
            'https://vanipedia.org/w/api.php';
        const CATEGORY =
            'Category:1080 English Pages with Videos';
        let videoPages = [];
        let currentVideo = '';
        /* --------------------------------------------------
          LOAD VIDEO PAGES
          -------------------------------------------------- */
        function loadVideoPages(cmcontinue) {
            let url =
                API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(CATEGORY) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=max';
            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }
            return fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {
                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        data.query.categorymembers
                            .forEach(function(page) {
                                if (
                                    /^Prabhupada \d{4}\s*-/i
                                        .test(page.title)
                                ) {
                                    videoPages.push(page);
                                }
                            });
                    }
                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadVideoPages(
                            data.continue.cmcontinue
                        );
                    }
                });
        }
        /* --------------------------------------------------
          CHOOSE RANDOM VIDEO
          -------------------------------------------------- */
        function chooseRandomVideoPage() {
            if (!videoPages.length) {
                return null;
            }
            let page;
            do {
                page =
                    videoPages[
                        Math.floor(
                            Math.random() *
                            videoPages.length
                        )
                    ];
            } while (
                videoPages.length > 1 &&
                page.title === currentVideo
            );
            return page;
        }
        /* --------------------------------------------------
          CLEAN TITLE
          -------------------------------------------------- */
        function getVideoDisplayTitle(pageTitle) {
            return pageTitle
                .replace(
                    /^Prabhupada\s+\d{4}\s*-\s*/i,
                    ''
                )
                .trim();
        }
        /* --------------------------------------------------
          VANIPEDIA PAGE URL
          -------------------------------------------------- */
        function getVideoPageUrl(pageTitle) {
            return (
                'https://vanipedia.org/wiki/' +
                encodeURIComponent(
                    pageTitle.replace(/ /g, '_')
                )
                    .replace(/%2F/g, '/')
                    .replace(/%3A/g, ':')
            );
        }
        /* --------------------------------------------------
          GET YOUTUBE ID FROM PAGE WIKITEXT
          -------------------------------------------------- */
        function loadVideoInfo(page) {
            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page.title) +
                '&prop=wikitext';
            return fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        return null;
                    }
                    const wiki =
                        data.parse.wikitext['*'];
                    /*
                    * Example:
                    * {{youtube_right|rEc1U_lsJ7s|Expand to Ten Million...}}
                    */
                    const match =
                        wiki.match(
                            /\{\{youtube_right\s*\|\s*([A-Za-z0-9_-]{11})\s*\|/i
                        );
                    const videoId =
                        match
                            ? match[1]
                            : '';
                    const thumbnailUrl =
                        videoId
                            ? 'https://i.ytimg.com/vi/' +
                              videoId +
                              '/hqdefault.jpg'
                            : '';
                    return {
                        pageUrl:
                            getVideoPageUrl(
                                page.title
                            ),
                        videoId:
                            videoId,
                        thumbnailUrl:
                            thumbnailUrl
                    };
                });
        }
        /* --------------------------------------------------
          SHOW RANDOM VIDEO
          -------------------------------------------------- */
        function showRandomVideo() {
            const page =
                chooseRandomVideoPage();
            if (!page) {
                return;
            }
            currentVideo =
                page.title;
            const media =
                videoCard.querySelector(
                    '.vh-feature-media'
                );
            const copy =
                videoCard.querySelector(
                    '.vh-feature-copy'
                );
            if (!media || !copy) {
                return;
            }
            /* ---------- title ---------- */
            copy.innerHTML = '';
            const type =
                document.createElement('small');
            type.textContent =
                'VIDEO';
            const title =
                document.createElement('strong');
            title.textContent =
                getVideoDisplayTitle(
                    page.title
                );
            copy.appendChild(type);
            copy.appendChild(title);
/* ---------- thumbnail + click ---------- */
loadVideoInfo(page)
    .then(function(info) {
        if (
            !info ||
            currentVideo !== page.title
        ) {
            return;
        }
        if (info.thumbnailUrl) {
            const preload =
                new Image();
            preload.onload =
                function() {
                    if (
                        currentVideo ===
                        page.title
                    ) {
                        media.style.backgroundImage =
                            'url("' +
                            info.thumbnailUrl +
                            '")';
                    }
                };
            preload.src =
                info.thumbnailUrl;
        }
        videoCard.onclick =
            function() {
                const target =
                    info.videoId
                        ? 'https://www.youtube.com/watch?v=' +
                          info.videoId
                        : info.pageUrl;
                window.open(
                    target,
                    '_blank',
                    'noopener'
                );
            };
    })
    .catch(function(error) {
        console.error(
            'Homepage Video failed:',
            error
        );
    });
}
        /* --------------------------------------------------
          START
          -------------------------------------------------- */
        loadVideoPages()
            .then(function() {
                console.log(
                    'Homepage Video pages loaded:',
                    videoPages.length
                );
                if (videoPages.length) {
                    showRandomVideo();
                    window.setInterval(
                        showRandomVideo,
                        11500
                    );
                }
            })
            .catch(function(error) {
                console.error(
                    'Homepage Video category failed:',
                    error
                );
            });
    }
}
/* ==========================================================
  HOMEPAGE — RANDOM IMAGE QUOTE
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const imageQuoteCard =
        document.getElementById('vh-random-imagequote');
    if (imageQuoteCard) {
        const IMAGE_API =
            'https://vanimedia.org/w/api.php';
        const MONTH_CATEGORIES = [
            'Category:January Quotes with Images',
            'Category:February Quotes with Images',
            'Category:March Quotes with Images',
            'Category:April Quotes with Images',
            'Category:May Quotes with Images',
            'Category:June Quotes with Images',
            'Category:July Quotes with Images',
            'Category:August Quotes with Images',
            'Category:September Quotes with Images',
            'Category:October Quotes with Images',
            'Category:November Quotes with Images',
            'Category:December Quotes with Images'
        ];
        let imageQuoteFiles = [];
        let currentImageQuote = '';
        /* ======================================================
          LOAD ONE MONTH CATEGORY
          ====================================================== */
        function loadImageQuoteCategory(
            category,
            cmcontinue
        ) {
            let url =
                IMAGE_API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(category) +
                '&cmnamespace=6' +
                '&cmtype=file' +
                '&cmlimit=max';
            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }
            return fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'Image Quote category HTTP ' +
                            response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        data.query.categorymembers
                            .forEach(function(file) {
                                if (file.title) {
                                    imageQuoteFiles.push(
                                        file
                                    );
                                }
                            });
                    }
                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadImageQuoteCategory(
                            category,
                            data.continue.cmcontinue
                        );
                    }
                });
        }
        /* ======================================================
          LOAD ALL MONTHS
          ====================================================== */
        function loadAllImageQuotes() {
            const jobs =
                MONTH_CATEGORIES.map(
                    function(category) {
                        return loadImageQuoteCategory(
                            category
                        );
                    }
                );
            return Promise.all(jobs);
        }
        /* ======================================================
          RANDOM FILE
          ====================================================== */
        function chooseRandomImageQuote() {
            if (!imageQuoteFiles.length) {
                return null;
            }
            let file;
            do {
                file =
                    imageQuoteFiles[
                        Math.floor(
                            Math.random() *
                            imageQuoteFiles.length
                        )
                    ];
            } while (
                imageQuoteFiles.length > 1 &&
                file.title === currentImageQuote
            );
            return file;
        }
        /* ======================================================
          GET THUMBNAIL + FULL IMAGE
          ====================================================== */
        function getImageQuoteInfo(file) {
            const url =
                IMAGE_API +
                '?action=query' +
                '&format=json' +
                '&prop=imageinfo' +
                '&iiprop=url' +
                '&iiurlwidth=500' +
                '&titles=' +
                encodeURIComponent(
                    file.title
                );
            return fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'Image Quote image HTTP ' +
                            response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.query ||
                        !data.query.pages
                    ) {
                        return null;
                    }
                    const page =
                        Object.values(
                            data.query.pages
                        )[0];
                    if (
                        !page ||
                        !page.imageinfo ||
                        !page.imageinfo.length
                    ) {
                        return null;
                    }
                    const info =
                        page.imageinfo[0];
                    const thumbnailUrl =
                        info.thumburl ||
                        info.url ||
                        '';
                    const fullUrl =
                        info.url ||
                        thumbnailUrl;
                    const pageUrl =
                        'https://vanimedia.org/wiki/' +
                        encodeURIComponent(
                            file.title.replace(
                                / /g,
                                '_'
                            )
                        )
                            .replace(/%2F/g, '/')
                            .replace(/%3A/g, ':');
                    return {
                        thumbnailUrl:
                            thumbnailUrl,
                        fullUrl:
                            fullUrl,
                        pageUrl:
                            pageUrl
                    };
                });
        }
        /* ======================================================
          FILENAME → HUMAN DATE
          072866 → July 28, 1966
          ====================================================== */
        function getImageQuoteTitle(
            fileTitle
        ) {
            const clean =
                fileTitle
                    .replace(
                        /^File:/i,
                        ''
                    )
                    .replace(
                        /\.(jpg|jpeg|png|webp)$/i,
                        ''
                    )
                    .replace(
                        /\s*Image-quote$/i,
                        ''
                    )
                    .trim();
            const match =
                clean.match(
                    /^(\d{2})(\d{2})(\d{2})/
                );
            if (!match) {
                return clean;
            }
            const month =
                parseInt(
                    match[1],
                    10
                );
            const day =
                parseInt(
                    match[2],
                    10
                );
            const year =
                1900 +
                parseInt(
                    match[3],
                    10
                );
            const months = [
                'January',
                'February',
                'March',
                'April',
                'May',
                'June',
                'July',
                'August',
                'September',
                'October',
                'November',
                'December'
            ];
            if (
                month < 1 ||
                month > 12 ||
                day < 1 ||
                day > 31
            ) {
                return clean;
            }
            return (
                months[month - 1] +
                ' ' +
                day +
                ', ' +
                year
            );
        }
        /* ======================================================
          LIGHTBOX
          ====================================================== */
        function openImageQuoteLightbox(
            imageUrl
        ) {
            let lightbox =
                document.getElementById(
                    'vh-imagequote-lightbox'
                );
            if (!lightbox) {
                lightbox =
                    document.createElement(
                        'div'
                    );
                lightbox.id =
                    'vh-imagequote-lightbox';
                lightbox.innerHTML =
                    '<button ' +
                    'class="vh-imagequote-lightbox-close" ' +
                    'type="button" ' +
                    'aria-label="Close">×</button>' +
                    '<img ' +
                    'alt="Śrīla Prabhupāda Image Quote">';
                document.body.appendChild(
                    lightbox
                );
lightbox.addEventListener(
    'mouseleave',
    function() {
        lightbox.classList.remove(
            'is-open'
        );
    }
);
                lightbox.addEventListener(
                    'click',
                    function(event) {
                        if (
                            event.target ===
                                lightbox ||
                            event.target
                                .classList
                                .contains(
                                    'vh-imagequote-lightbox-close'
                                )
                        ) {
                            lightbox.classList
                                .remove(
                                    'is-open'
                                );
                        }
                    }
                );
                document.addEventListener(
                    'keydown',
                    function(event) {
                        if (
                            event.key ===
                            'Escape'
                        ) {
                            lightbox.classList
                                .remove(
                                    'is-open'
                                );
                        }
                    }
                );
            }
            const image =
                lightbox.querySelector(
                    'img'
                );
            image.src =
                imageUrl;
const cardRect =
    imageQuoteCard.getBoundingClientRect();
lightbox.classList.add(
    'is-open'
);
requestAnimationFrame(function() {
    const popupRect =
        lightbox.getBoundingClientRect();
    /*
    * Place popup immediately to the
    * right of the Image Quote card.
    */
    let left =
        cardRect.right - 240;
    let top =
        cardRect.top - 220;
    /*
    * If there is not enough room
    * on the right, place it above.
    */
    if (
        left +
        popupRect.width >
        window.innerWidth - 10
    ) {
        left =
            cardRect.right -
            popupRect.width;
        top =
            cardRect.top -
            popupRect.height -
            12;
    }
    /*
    * Keep inside viewport.
    */
    left =
        Math.max(
            10,
            Math.min(
                left,
                window.innerWidth -
                popupRect.width -
                10
            )
        );
    top =
        Math.max(
            10,
            top
        );
    lightbox.style.left =
        left + 'px';
    lightbox.style.top =
        top + 'px';
});
        }
        /* ======================================================
          DISPLAY RANDOM IMAGE QUOTE
          ====================================================== */
        function showRandomImageQuote() {
            const file =
                chooseRandomImageQuote();
            if (!file) {
                return;
            }
            currentImageQuote =
                file.title;
            const media =
                imageQuoteCard.querySelector(
                    '.vh-feature-media'
                );
            const copy =
                imageQuoteCard.querySelector(
                    '.vh-feature-copy'
                );
            if (!media || !copy) {
                return;
            }
            /*
            * Card text
            */
            copy.innerHTML = '';
            const type =
                document.createElement(
                    'small'
                );
            type.textContent =
                'IMAGE QUOTE';
            const title =
                document.createElement(
                    'strong'
                );
            title.textContent =
                getImageQuoteTitle(
                    file.title
                );
            copy.appendChild(
                type
            );
            copy.appendChild(
                title
            );
            /*
            * Load image
            */
            getImageQuoteInfo(
                file
            )
                .then(function(info) {
                    if (
                        !info ||
                        currentImageQuote !==
                            file.title
                    ) {
                        return;
                    }
                    if (
                        info.thumbnailUrl
                    ) {
                        const preload =
                            new Image();
                        preload.onload =
                            function() {
                                if (
                                    currentImageQuote ===
                                    file.title
                                ) {
                                    media.style
                                        .backgroundImage =
                                        'url("' +
                                        info.thumbnailUrl +
                                        '")';
                                }
                            };
                        preload.src =
                            info.thumbnailUrl;
                    }
                    /*
                    * Open full image
                    * in lightbox
                    */
                    imageQuoteCard.onclick =
                        function() {
                            if (
                                info.fullUrl
                            ) {
                                openImageQuoteLightbox(
                                    info.fullUrl
                                );
                            }
                        };
                })
                .catch(function(error) {
                    console.error(
                        'Homepage Image Quote failed:',
                        error
                    );
                });
        }
        /* ======================================================
          START
          ====================================================== */
        loadAllImageQuotes()
            .then(function() {
                console.log(
                    'Homepage Image Quotes loaded:',
                    imageQuoteFiles.length
                );
                if (
                    imageQuoteFiles.length
                ) {
                    showRandomImageQuote();
                    window.setInterval(
                        showRandomImageQuote,
                        9800
                    );
                }
            })
            .catch(function(error) {
                console.error(
                    'Homepage Image Quote categories failed:',
                    error
                );
            });
    }
}
/* ==========================================================
  HOMEPAGE — RANDOM NECTAR DROP
  ========================================================== */
if (mw.config.get('wgPageName') === 'Main_Page') {
    const nectarCard =
        document.getElementById('vh-random-nectar');
    if (nectarCard) {
        const NECTAR_API =
            'https://vanipedia.org/w/api.php';
        const NECTAR_CATEGORY =
            'Category:Nectar Drops from Srila Prabhupada';
        let nectarPages = [];
        let currentNectar = '';
        let nectarAudio = null;
        let nectarLocked = false;
        const audioArea =
            nectarCard.querySelector(
                '.vh-feature-audio'
            );
        const audioIcon =
            nectarCard.querySelector(
                '.vh-feature-audio-icon'
            );
const nectarWave =
    document.createElement(
        'div'
    );
nectarWave.className =
    'vh-nectar-wave';
nectarWave.innerHTML =
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>';
audioArea.appendChild(
    nectarWave
);
        const copy =
            nectarCard.querySelector(
                '.vh-feature-copy'
            );
        /* ======================================================
          LOAD ALL NECTAR DROP PAGES
          ====================================================== */
        function loadNectarPages(
            cmcontinue
        ) {
            let url =
                NECTAR_API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(
                    NECTAR_CATEGORY
                ) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=max';
            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(
                        cmcontinue
                    );
            }
            return fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'Nectar category HTTP ' +
                            response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        data.query.categorymembers
                            .forEach(function(page) {
                                if (page.title) {
                                    nectarPages.push(
                                        page
                                    );
                                }
                            });
                    }
                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadNectarPages(
                            data.continue.cmcontinue
                        );
                    }
                });
        }
        /* ======================================================
          CHOOSE RANDOM NECTAR DROP
          ====================================================== */
        function chooseRandomNectar() {
            if (!nectarPages.length) {
                return null;
            }
            let page;
            do {
                page =
                    nectarPages[
                        Math.floor(
                            Math.random() *
                            nectarPages.length
                        )
                    ];
            } while (
                nectarPages.length > 1 &&
                page.title === currentNectar
            );
            return page;
        }
        /* ======================================================
          CREATE SHORT MEANINGFUL TITLE FROM QUOTE
          ====================================================== */
        function getNectarShortText(
            quote
        ) {
            if (!quote) {
                return 'Śrīla Prabhupāda speaks';
            }
            let text =
                quote
                    .replace(
                        /^["“”]+|["“”]+$/g,
                        ''
                    )
                    .replace(
                        /\s+/g,
                        ' '
                    )
                    .trim();
            /*
            * Prefer first sentence when it is
            * reasonably short.
            */
            const sentenceMatch =
                text.match(
                    /^(.+?[.!?])(?:\s|$)/
                );
            if (
                sentenceMatch &&
                sentenceMatch[1].length <= 85
            ) {
                return sentenceMatch[1];
            }
            /*
            * Otherwise take approximately
            * the first 70 characters,
            * ending at a full word.
            */
            if (text.length > 72) {
                text =
                    text.substring(
                        0,
                        72
                    );
                const lastSpace =
                    text.lastIndexOf(
                        ' '
                    );
                if (lastSpace > 45) {
                    text =
                        text.substring(
                            0,
                            lastSpace
                        );
                }
                text += '…';
            }
            return text;
        }
        /* ======================================================
          GET QUOTE TEXT + AUDIO URL
          ====================================================== */
        function getNectarInfo(
            page
        ) {
            const url =
                NECTAR_API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(
                    page.title
                ) +
                '&prop=text';
            return fetch(url)
                .then(function(response) {
                    if (!response.ok) {
                        throw new Error(
                            'Nectar page HTTP ' +
                            response.status
                        );
                    }
                    return response.json();
                })
                .then(function(data) {
                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return null;
                    }
                    const wrapper =
                        document.createElement(
                            'div'
                        );
                    wrapper.innerHTML =
                        data.parse.text['*'];
                    /*
                    * Quote
                    */
                    const quoteElement =
                        wrapper.querySelector(
                            '.nectardroptext'
                        );
                    const quote =
                        quoteElement
                            ? quoteElement
                                .textContent
                                .replace(
                                    /\s+/g,
                                    ' '
                                )
                                .trim()
                            : '';
                    /*
                    * MP3 source
                    */
                    const source =
                        wrapper.querySelector(
                            'audio source'
                        );
                    const audioUrl =
                        source
                            ? source.getAttribute(
                                'src'
                            )
                            : '';
                    return {
                        quote:
                            quote,
                        audioUrl:
                            audioUrl
                    };
                });
        }
        /* ======================================================
          RESET PLAY BUTTON
          ====================================================== */
        function resetNectarPlayButton() {
            if (audioIcon) {
                audioIcon.textContent =
                    '▶';
            }
        }
        /* ======================================================
          PREPARE AUDIO
          ====================================================== */
        function prepareNectarAudio(
            audioUrl
        ) {
            /*
            * Dispose of previous audio object.
            */
            if (nectarAudio) {
                nectarAudio.pause();
                nectarAudio.src = '';
                nectarAudio = null;
            }
            resetNectarPlayButton();
            if (!audioUrl) {
                return;
            }
            nectarAudio =
                new Audio(
                    audioUrl
                );
            nectarAudio.preload =
                'metadata';
            /*
            * Audio finished naturally.
            */
/*
* Audio finished naturally.
*/
nectarAudio.addEventListener(
    'ended',
    function() {
        resetNectarPlayButton();
        nectarWave.classList.remove(
            'is-playing'
        );
        nectarLocked =
            false;
    }
);
/*
* Audio starts / resumes.
*/
nectarAudio.addEventListener(
    'play',
    function() {
        if (audioIcon) {
            audioIcon.textContent =
                '❚❚';
        }
        nectarWave.classList.add(
            'is-playing'
        );
    }
);
/*
* Audio pauses.
*/
nectarAudio.addEventListener(
    'pause',
    function() {
        if (
            nectarAudio &&
            !nectarAudio.ended &&
            audioIcon
        ) {
            audioIcon.textContent =
                '▶';
        }
        nectarWave.classList.remove(
            'is-playing'
        );
    }
);
}
        /* ======================================================
          PLAY / PAUSE
          ====================================================== */
        function toggleNectarAudio(
            event
        ) {
            if (event) {
                event.preventDefault();
                event.stopPropagation();
            }
            if (!nectarAudio) {
                return;
            }
            /*
            * Playing → pause
            */
if (!nectarAudio.paused) {
    nectarAudio.pause();
    nectarLocked =
        false;
    return;
}
            /*
            * Paused → play/resume
            */
            nectarLocked =
                true;
            nectarAudio
                .play()
                .catch(function(error) {
                    console.error(
                        'Nectar Drop audio failed:',
                        error
                    );
                });
        }
        /* ======================================================
          DISPLAY RANDOM NECTAR DROP
          ====================================================== */
        function showRandomNectar() {
            /*
            * Never change the card while
            * user is listening or has paused it.
            */
            if (nectarLocked) {
                return;
            }
            const page =
                chooseRandomNectar();
            if (!page) {
                return;
            }
            currentNectar =
                page.title;
            if (!copy) {
                return;
            }
            copy.innerHTML = '';
            const type =
                document.createElement(
                    'small'
                );
            type.textContent =
                'NECTAR DROP';
            const title =
                document.createElement(
                    'strong'
                );
            title.textContent =
                '';
            copy.appendChild(
                type
            );
            copy.appendChild(
                title
            );
            getNectarInfo(
                page
            )
                .then(function(info) {
                    if (
                        !info ||
                        currentNectar !==
                            page.title
                    ) {
                        return;
                    }
                    /*
                    * Display meaningful words
                    * directly from Nectar Drop.
                    */
                    title.textContent =
                        getNectarShortText(
                            info.quote
                        );
                    /*
                    * Prepare MP3.
                    */
                    prepareNectarAudio(
                        info.audioUrl
                    );
                })
                .catch(function(error) {
                    console.error(
                        'Homepage Nectar Drop failed:',
                        error
                    );
                    title.textContent =
                        'Śrīla Prabhupāda speaks';
                });
        }
        /* ======================================================
          PLAY BUTTON CLICK
          ====================================================== */
        if (audioArea) {
            audioArea.addEventListener(
                'click',
                toggleNectarAudio
            );
        }
        /* ======================================================
          START
          ====================================================== */
        loadNectarPages()
            .then(function() {
                console.log(
                    'Homepage Nectar Drops loaded:',
                    nectarPages.length
                );
                if (
                    nectarPages.length
                ) {
                    showRandomNectar();
                    window.setInterval(
                        function() {
                            if (!nectarLocked) {
                                showRandomNectar();
                            }
                        },
                        12700
                    );
                }
            })
            .catch(function(error) {
                console.error(
                    'Homepage Nectar category failed:',
                    error
                );
            });
    }
}
const networkMission =
    document.getElementById('vh-network-mission');
if (networkMission) {
    networkMission.addEventListener(
        'click',
        function() {
            window.location.href =
                mw.util.getUrl('Vanipedia_Vision');
        }
    );
}
/* CLOSE HOMEPAGE WRAPPER */
})();
/* =========================================================
  VANIPEDIA FLIPBOOK POPUP
  ========================================================= */
(function () {
    function createFlipbookOverlay() {
        if (document.getElementById('vani-flipbook-overlay')) {
            return;
        }
        var overlay = document.createElement('div');
        overlay.id = 'vani-flipbook-overlay';
        var modal = document.createElement('div');
        modal.className = 'vani-flipbook-modal';
        var closeButton = document.createElement('button');
        closeButton.className = 'vani-flipbook-close';
        closeButton.type = 'button';
        closeButton.setAttribute('aria-label', 'Close flipbook');
        closeButton.innerHTML = '&times;';
        var frameHolder = document.createElement('div');
        frameHolder.id = 'vani-flipbook-frame-holder';
        modal.appendChild(closeButton);
        modal.appendChild(frameHolder);
        overlay.appendChild(modal);
        document.body.appendChild(overlay);
        closeButton.addEventListener('click', function () {
            closeFlipbook();
        });
        overlay.addEventListener('click', function (event) {
            if (event.target === overlay) {
                closeFlipbook();
            }
        });
        document.addEventListener('keydown', function (event) {
            if (
                event.key === 'Escape' &&
                overlay.classList.contains('is-open')
            ) {
                closeFlipbook();
            }
        });
    }
    function openFlipbook(book) {
        createFlipbookOverlay();
        var overlay =
            document.getElementById('vani-flipbook-overlay');
        var holder =
            document.getElementById('vani-flipbook-frame-holder');
        /*
        * FIRST make popup visible.
        */
        overlay.classList.add('is-open');
        document.body.classList.add('vani-flipbook-open');
        /*
        * Remove any previous iframe.
        */
        holder.innerHTML = '';
        /*
        * Wait until browser has calculated
        * the real popup dimensions.
        */
        requestAnimationFrame(function () {
            requestAnimationFrame(function () {
                var frame =
                    document.createElement('iframe');
                frame.id =
                    'vani-flipbook-frame';
                frame.title =
                    'Vanipedia Flipbook';
                frame.setAttribute(
                    'allowfullscreen',
                    'true'
                );
frame.src =
    'https://dev.vanipedia.org/flipbook/?book=' +
    encodeURIComponent(book);
                holder.appendChild(frame);
            });
        });
    }
    function closeFlipbook() {
        var overlay =
            document.getElementById('vani-flipbook-overlay');
        var holder =
            document.getElementById('vani-flipbook-frame-holder');
        if (!overlay) {
            return;
        }
        overlay.classList.remove('is-open');
        document.body.classList.remove('vani-flipbook-open');
        if (holder) {
            holder.innerHTML = '';
        }
    }
    function initialiseFlipbooks() {
        var triggers =
            document.querySelectorAll(
                '.vani-flipbook-trigger'
            );
        triggers.forEach(function (trigger) {
            if (
                trigger.getAttribute(
                    'data-flipbook-ready'
                ) === '1'
            ) {
                return;
            }
            trigger.setAttribute(
                'data-flipbook-ready',
                '1'
            );
            trigger.addEventListener(
                'click',
                function (event) {
                    event.preventDefault();
                    var book =
                        trigger.getAttribute(
                            'data-book'
                        );
                    if (book) {
                        openFlipbook(book);
                    }
                }
            );
        });
    }
    createFlipbookOverlay();
    initialiseFlipbooks();
    if (
        window.mw &&
        mw.hook
    ) {
        mw.hook(
            'wikipage.content'
        ).add(function () {
            initialiseFlipbooks();
        });
    }
})();

Revision as of 11:29, 16 September 2026

/* Any JavaScript here will be loaded for all users on every page load. */

if (mw.config.get('wgPageName' ) === 'Krishna_says_in_Bhagavad-gita') { 
  /* switch tooltip texts from content-div to label-div */
  const v_labels = Array.from(document.getElementsByClassName("tabs-label"));
  const v_content = Array.from(document.getElementsByClassName("tabs-content"));
  for (var i = 0; i < v_labels.length; i++) {
    v_labels[i].title = v_content[i].title;
    v_content[i].title = "";
  }
  /* adjust wikitables */
  var arr = Array.from(document.getElementsByClassName("wikitable"));
  for (var t = 0; t < arr.length; t++) {
    /* insert THEAD in wikitable */
    var thead = document.createElement("thead");
    thead.style.display = "block";
    var wtbl = arr[t];
    wtbl.insertBefore(thead, wtbl.firstChild);
    /* move first row of TBODY to THEAD */
    thead.appendChild(wtbl.rows[0]);
    /* make TBODY scrollable */
    var tbody = wtbl.tBodies[0];
    tbody.style.display = "block";
    tbody.style.overflowY = "auto";
    tbody.style.maxHeight = "500px";    
    for (var r = 0; r < wtbl.rows.length; r++) {
      var row = wtbl.rows[r];
      /* adjust column widths and hide from-until columns */
      row.cells[0].classList = "cw_0";
      row.cells[1].classList = "cw_150";
      row.cells[2].classList = "cw_500";
      /* mark the text */ 
      if (r == 0) continue;
      var v_text = row.cells[2].textContent;
      var v_html = ""; var v_from = 0;
      var marks = JSON.parse(row.cells[0].textContent.replaceAll("O", "[").replaceAll("C", "]"));
      for (var m = 0; m < marks.length; m++) {
        v_html += 
          v_text.substr(v_from,marks[m][0]-v_from) + "<mark>" +
          v_text.substr(marks[m][0],marks[m][1]-marks[m][0]+1) + "</mark>";
        v_from = marks[m][1]+1;
      }
      v_html += v_text.substr(v_from);
      row.cells[2].innerHTML = v_html;
    }
  }
}

// install Google Search
// document.write('<script type="text/javascript" src="'
// + 'http://vanipedia.org/w/index.php?title=MediaWiki:GoogleSearch.js'
// + '&action=raw&ctype=text/javascript"></' + 'script>');

// install JQuery
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:JQuery.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');

// install WikEd toolbar and Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vaniquotes.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
 
// install Replace
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:TextboxReplace.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
 
// install Unify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Unify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');
 
// install Vanify
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vanify.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');

// install Vani_suggest for diacritics auto-suggest pulldown on the search bar
//document.write('<script type="text/javascript" src="'
//+ 'http://vanipedia.org/w/index.php?title=MediaWiki:Vani_suggest.js'
//+ '&action=raw&ctype=text/javascript"></' + 'script>');

// install previous/next links for video categories in all languages
 //document.write('<script type="text/javascript" src="/navcat/navcat.js"></' + 'script>');
 
// Buttons
 
//addOnloadHook(function() {
 
//if (document.editform) {
//addPortletLink("p-cactions", "javascript:Unify()", "unify", "ca-unify", "Convert Balarama fonts to Unicode", "");
// addPortletLink("p-cactions", "javascript:Vanify()", "vanify", "ca-vanify", "Corrects vani markup", "");
// addPortletLink('p-cactions', 'javascript:format()', 'format', 'ca-format', 'Format article', '');
//addPortletLink('p-cactions', 'javascript:wpTextboxReplace()', 'Replace', 'ca-replace', 'Replaces text in the edit window', 'R');
//}
//});
 
// end Buttons

// --- YouTube Random Shorts (Kasya) ---
$(document).ready(function() {
    var $widget = $('.vani-shorts-viewport');
    
    if ($widget.length) {
        var $viewport = $widget;
        var $titleFrame = $widget.find('.vani-shorts-title');
        
        new mw.Api().get({
            action: 'query',
            prop: 'revisions',
            titles: 'VanimediaMayapur YouTube SHORTS List',
            rvprop: 'content',
            format: 'json'
        }).done(function(data) {
            try {
                var pages = data.query.pages;
                var pageId = Object.keys(pages)[0];
                
                if (pageId == "-1" || !pages[pageId].revisions) {
                    $viewport.text("Database list page not found.");
                    return;
                }
                
                // FIXED: Corrected MediaWiki revision array syntax lookup
                var rawWikiText = pages[pageId].revisions[0]['*'];
                var lines = rawWikiText.split('\n');
                var validShorts = [];
                
                for (var i = 0; i < lines.length; i++) {
                    var line = $.trim(lines[i]);
                    if (line.indexOf('File:') === 0 && line.toLowerCase().indexOf('link=') !== -1) {
                        validShorts.push(line);
                    }
                }
                
                if (validShorts.length === 0) {
                    $viewport.text("No gallery entries recognized.");
                    return;
                }
                
                var randomLine = validShorts[Math.floor(Math.random() * validShorts.length)];
                var parts = randomLine.split('|');
                
                // Reset defaults before parsing line elements
                var rawUrl = "";
                
                for (var j = 0; j < parts.length; j++) {
                    var part = $.trim(parts[j]);
                    
                    if (part.toLowerCase().indexOf('link=') === 0) {
                        rawUrl = part.substring(5).trim();
                    }
                }
                
                // Clean all template formatting brackets, space characters, and hidden control markers
                var urlCleaned = rawUrl.replace(/[\u200e\u200f\s\}\}\]]/g, ''); 
                
                // Direct YouTube Alphanumeric Pattern Matcher
                var idRegex = /(?:shorts\/|v=|embed\/|\/)([a-zA-Z0-9_-]{11})/;
                var matchResults = urlCleaned.match(idRegex);
                var videoId = "";
                
                if (matchResults && matchResults[1]) {
                    // FIXED: Grabbed index 1 to pull the standalone 11-digit ID without any slashes
                    videoId = matchResults[1].trim();
                }
                
                if (videoId && videoId.length === 11) {
                    // FIXED: Isolated to inject ONLY the video player iframe at the correct 280px width
                   var embedUrl = "https://www.youtube.com/embed/" + videoId;

                    $viewport.html('<iframe width="280" height="225" src="' + embedUrl + '" frameborder="0" allowfullscreen style="border-radius: 8px; border: none;"></iframe>');
                } else {
                    $viewport.html("<div style='color:#c0392b; padding:10px; font-size:11px;'>Invalid video ID sequence extracted.</div>");
                }
                
            } catch (err) {
                console.error("Shorts engine exception:", err);
                $viewport.text("Error handling text structure values.");
            }
        }).fail(function() {
            $viewport.text("API connection failed.");
        });
    }
});

// Kasya
/* Open Timeline links in a new tab ONLY on the Chapters page */
mw.hook('wikipage.content').add(function($content) {
    // Check if we are exactly on the timeline page
    if (mw.config.get('wgPageName') === "Chapters_in_Srila_Prabhupada's_Life") {
        
        // If yes, apply the new tab rule to the table
        $content.find('#mp-tfp a').attr('target', '_blank').attr('rel', 'noopener noreferrer');
    }
});

// Wait for MediaWiki core modules and the DOM to load
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
    $(document).ready(function () {
        
// 1. FIND THE "102 Languages" SIDEBAR SECTION
        var $sidebarMenu = $('#p-102_Languages ul').first();
        
        // Fallback in case your specific MediaWiki version formats the ID differently
        if ($sidebarMenu.length === 0) {
            $('.vector-menu-heading, .portal h3').each(function() {
                // Looks for the heading text directly
                if ($(this).text().trim() === '102 Languages') {
                    $sidebarMenu = $(this).closest('.vector-menu, .portal').find('ul').first();
                }
            });
        }
        
        // Exit if the 102 Languages menu isn't found on the page
        if (!$sidebarMenu || $sidebarMenu.length === 0) return;

        // 2. CREATE THE SEARCH BAR AS A MENU ITEM
        var $searchLi = $('<li id="n-language-search" style="margin-top: 10px; margin-bottom: 5px; list-style: none;"></li>');
        var $searchBox = $('<input type="text" id="sidebarLanguageSearch" placeholder="Search 102 Languages..." style="width: 100%; box-sizing: border-box; padding: 5px; font-size: 0.85em; border: 1px solid #a2a9b1; border-radius: 2px;" autocomplete="off" />');
        
        // Container for the results (hidden by default)
        var $resultsContainer = $('<ul id="sidebarLanguageResults" style="list-style: none; padding: 0; margin: 4px 0 0 0; max-height: 200px; overflow-y: auto; border: 1px solid #eaecf0; background: #ffffff; display: none;"></ul>');

        // Build the menu item and append it to the sidebar
        $searchLi.append($searchBox).append($resultsContainer);
        $sidebarMenu.prepend($searchLi); 

        var api = new mw.Api();
        var languageList = [];

        // 3. FETCH LANGUAGES FROM THE CATEGORY VIA API IN THE BACKGROUND
        api.get({
            action: 'query',
            list: 'categorymembers',
            cmtitle: 'Category:Languages_with_Total_Pages',
            cmlimit: 'max', 
            format: 'json'
        }).done(function (data) {
            if (data.query && data.query.categorymembers) {
                languageList = data.query.categorymembers.map(function (member) {
                    return {
                        title: member.title,
                        url: mw.util.getUrl(member.title) 
                    };
                });
            }
        });

        // 4. ADD SEARCH FUNCTIONALITY TO THE SIDEBAR BOX
        $('#sidebarLanguageSearch').on('keyup', function (e) {
            e.stopPropagation(); 
            var searchTerm = $(this).val().toLowerCase();
            $resultsContainer.empty();

            // Hide the results box if the search is empty
            if (searchTerm.length === 0) {
                $resultsContainer.hide();
                return; 
            }

            // Filter the fetched languages
            var filteredLanguages = languageList.filter(function (lang) {
                return lang.title.toLowerCase().indexOf(searchTerm) > -1;
            });

            // Show results container
            $resultsContainer.show();

            // Append results using Vector's standard dark text color
            if (filteredLanguages.length > 0) {
                filteredLanguages.forEach(function (lang) {
                    $resultsContainer.append('<li style="padding: 4px 6px; border-bottom: 1px solid #f8f9fa;"><a href="' + lang.url + '" style="text-decoration: none; color: #0645ad; display: block; font-size: 0.9em;">' + lang.title + '</a></li>');
                });
            } else {
                $resultsContainer.append('<li style="padding: 4px 6px; color: #72777d; font-size: 0.9em;">No languages found</li>');
            }
        });

        // Prevent typing from accidentally triggering global keyboard shortcuts
        $('#sidebarLanguageSearch').on('keydown', function(e) {
            e.stopPropagation();
        });
        
        // Optional: Hide results when clicking outside the sidebar search
        $(document).on('click', function(e) {
            if (!$(e.target).closest('#n-language-search').length) {
                $resultsContainer.hide();
            }
        });
        
        // Re-show results if they click back into the search box
        $('#sidebarLanguageSearch').on('click', function() {
            if ($(this).val().length > 0) {
                $resultsContainer.show();
            }
        });
        
    });
});

// 5. SIDEBAR ACCORDION - STRICT LEFT-COLUMN ISOLATION & TRUE ACCORDION
(function() {
    // 1. Tag ONLY menus physically located in the left sidebar
    function tagSidebarMenus() {
        var allMenus = document.querySelectorAll('.vector-menu');
        for (var i = 0; i < allMenus.length; i++) {
            var menu = allMenus[i];
            
            // STRICT INCLUSION: Only tag menus inside the modern or legacy left sidebar containers
            if (menu.closest('#vector-main-menu, #mw-panel, #mw-navigation')) {
                menu.classList.add('vanipedia-sidebar-menu');
                
                // Collapse them by default if they have a heading
                if (menu.querySelector('.vector-menu-heading') && !menu.classList.contains('vanipedia-collapsed')) {
                    menu.classList.add('vanipedia-collapsed');
                }
            }
        }
    }
    
    // Run tagging immediately, and again on load as a failsafe
    tagSidebarMenus();
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', tagSidebarMenus);
    }

    // 2. Inject CSS targeted STRICTLY at our newly tagged custom class
    var style = document.createElement('style');
    style.textContent = 
        /* Widen only our tagged sidebar menus */
        '.vanipedia-sidebar-menu { width: 185px !important; } ' + 
        
        /* Core functional hiding rule */
        '.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-content { display: none !important; } ' + 
        
        /* Bold, Dark Header */
        '.vanipedia-sidebar-menu .vector-menu-heading { ' +
        '   cursor: pointer !important; ' +
        '   background-color: #2c4c50 !important; ' +     
        '   border: 1px solid #1b3033 !important; ' +     
        '   border-radius: 4px !important; ' +            
        '   margin-bottom: 4px !important; ' +            
        '   padding: 10px 12px !important; ' +
        '   font-weight: 600 !important; ' +
        '   color: #ffffff !important; ' +                
        '   display: flex !important; ' +
        '   justify-content: space-between !important; ' + 
        '   align-items: center !important; ' +
        '   gap: 10px !important; ' + 
        '   transition: all 0.2s ease; ' +
        '} ' +
        
        /* Fix header corners when expanded */
        '.vanipedia-sidebar-menu:not(.vanipedia-collapsed) .vector-menu-heading { ' +
        '   border-radius: 4px 4px 0 0 !important; ' +
        '   margin-bottom: 0 !important; ' +
        '} ' +
        
        /* Hover effect */
        '.vanipedia-sidebar-menu .vector-menu-heading:hover { background-color: #385e63 !important; } ' +
        
        /* The dropdown caret */
        '.vanipedia-sidebar-menu .vector-menu-heading::after { ' +
        '   content: "▼"; ' + 
        '   font-size: 0.75em; ' +
        '   color: #ffffff !important; ' +
        '   transition: transform 0.2s ease; ' +
        '} ' +
        
        /* Rotate the arrow when collapsed */
        '.vanipedia-sidebar-menu.vanipedia-collapsed .vector-menu-heading::after { ' +
        '   transform: rotate(-90deg); ' +
        '} ' +

        /* The Expanded Content Area */
        '.vanipedia-sidebar-menu .vector-menu-content { ' +
        '   background-color: #eaeff0 !important; ' +     
        '   border: 1px solid #2c4c50 !important; ' +     
        '   border-top: none !important; ' +              
        '   border-radius: 0 0 4px 4px !important; ' +    
        '   padding: 0 0 4px 0 !important; ' + 
        '   margin-bottom: 6px !important; ' +            
        '   box-shadow: 0 3px 6px rgba(0,0,0,0.15) !important; ' + 
        '} ' +

        /* Reset the list styles */
        '.vanipedia-sidebar-menu .vector-menu-content ul { ' +
        '   list-style: none !important; ' +
        '   margin: 0 !important; ' +
        '   padding: 0 !important; ' +
        '} ' +

        /* Menu links */
        '.vanipedia-sidebar-menu .vector-menu-content li a { ' +
        '   display: block !important; ' +                
        '   padding: 8px 12px 8px 16px !important; ' +    
        '   color: #1b3033 !important; ' +                
        '   text-decoration: none !important; ' +         
        '   border-left: 3px solid transparent !important; ' + 
        '   border-bottom: 1px solid rgba(44, 76, 80, 0.1) !important; ' + 
        '   transition: all 0.15s ease !important; ' +
        '} ' +
        
        /* Remove separator from last item */
        '.vanipedia-sidebar-menu .vector-menu-content li:last-child a { ' +
        '   border-bottom: none !important; ' +
        '} ' +

        /* Hover effect for individual menu items */
        '.vanipedia-sidebar-menu .vector-menu-content li a:hover { ' +
        '   background-color: #dbe4e6 !important; ' +     
        '   border-left: 3px solid #2c4c50 !important; ' + 
        '   color: #000000 !important; ' +                
        '} ';
        
    document.head.appendChild(style);

    // 3. Helper function to collapse all of our securely tagged menus
    function collapseAllSidebarMenus() {
        var menus = document.querySelectorAll('.vanipedia-sidebar-menu');
        for (var i = 0; i < menus.length; i++) {
            menus[i].classList.add('vanipedia-collapsed');
        }
    }

    // 4. Intercept clicks for True Accordion Behavior
    document.addEventListener('click', function(e) {
        var heading = e.target.closest('.vector-menu-heading');
        if (!heading) return;

        // ONLY react if this click belongs to one of our tagged sidebar menus
        var navContainer = heading.closest('.vanipedia-sidebar-menu');
        if (navContainer) {
            var isAlreadyOpen = !navContainer.classList.contains('vanipedia-collapsed');
            
            // Forcefully collapse ALL left sidebar menus first
            collapseAllSidebarMenus();

            // Open the clicked one if it wasn't already open
            if (!isAlreadyOpen) {
                navContainer.classList.remove('vanipedia-collapsed');
            }
            
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);
})();

mw.loader.using('mediawiki.user').then(function() {
    // Fetch the user's assigned groups
    var userGroups = mw.config.get('wgUserGroups') || [];
    
    // Define the roles that SHOULD see the administrative tabs
    var isAuthorized = userGroups.includes('sysop') || 
                       userGroups.includes('interface-admin') || 
                       userGroups.includes('Editor');
                       
    // If the user is not in one of those groups, restrict the UI
    if (!isAuthorized) {
        document.body.classList.add('restricted-ui');
    }
});

// Function to open accordion from TOC and jump there //

mw.hook('wikipage.content').add(function() {
    
    function handleAccordionTOC(hash) {
        if (!hash || hash === '#') return false;
        
        try {
            var targetId = hash.substring(1);
            var targetElement = document.getElementById(targetId) || document.getElementById(decodeURIComponent(targetId));
            
            if (!targetElement) return false;

            var $target = $(targetElement);
            // Match either h2 or h3
            var $heading = $target.is('h2, h3') ? $target : $target.closest('h2, h3');
            
            if (!$heading.length) return false;

            var $accordion = $heading.next('.book-accordion');
            
            // If the heading itself is not followed directly by an accordion, 
            // check if the heading is located INSIDE a parent accordion (e.g., nested h3)
            var $parentAccordions = $heading.parents('.book-accordion');

            // If there's no direct accordion next to it and it's not inside any accordion,
            // let the browser handle standard jump.
            if (!$accordion.length && !$parentAccordions.length) return false;

            // 1. Open all ancestor accordions (if target heading is nested inside one)
            $parentAccordions.each(function() {
                var $parentCollapsible = $(this).children('.mw-collapsible');
                if ($parentCollapsible.length && $parentCollapsible.hasClass('mw-collapsed')) {
                    var pId = $parentCollapsible.attr('id');
                    if (pId) {
                        var pToggleClass = pId.replace('mw-customcollapsible-', 'mw-customtoggle-');
                        $('.' + pToggleClass).first().click();
                    }
                }
            });

            // 2. Open the accordion immediately following this heading (if present)
            if ($accordion.length) {
                var $collapsible = $accordion.children('.mw-collapsible, .mw-collapsible-content').first();
                if (!$collapsible.length) {
                    $collapsible = $accordion.find('.mw-collapsible');
                }
                
                if ($collapsible.length && $collapsible.hasClass('mw-collapsed')) {
                    var collapsibleId = $collapsible.attr('id'); 
                    if (collapsibleId) {
                        var toggleClass = collapsibleId.replace('mw-customcollapsible-', 'mw-customtoggle-');
                        $('.' + toggleClass).first().click();
                    }
                }
            }

            // Target element to scroll to (the accordion, or the heading itself if accordion is missing)
            var $scrollTarget = $accordion.length ? $accordion : $heading;

            // 3. Scroll to the visible section
            setTimeout(function() {
                $('html, body').animate({
                    scrollTop: $scrollTarget.offset().top - 75
                }, 300);
            }, 100);

            // Return true to indicate we successfully handled the TOC accordion click
            return true;

        } catch (e) {
            console.error("TOC Accordion script error:", e);
            return false;
        }
    }

    $(document).off('click.tocAccordion').on('click.tocAccordion', 'a[href^="#"]', function(e) {
        if ($(this).closest('#toc, .toc, .vector-toc').length > 0) {
            var href = $(this).attr('href');
            
            // Run our function to see if this is an accordion/heading link
            var isAccordion = handleAccordionTOC(href);
            
            // ONLY stop the default browser jump if we handled it
            if (isAccordion) {
                e.preventDefault(); 
                
                // Update the URL manually so the user can still share the link
                if (history.pushState) {
                    history.pushState(null, null, href);
                } else {
                    window.location.hash = href;
                }
            }
        }
    });

    if (window.location.hash) {
        setTimeout(function() {
            handleAccordionTOC(window.location.hash);
        }, 500);
    }
});

/* Sticky burger menu - Pure Floating Dropdown (Forces Unpinned & Full Title) */
(function () {
    let isMenuOpen = false;

    // 1. Force the sidebar out of pinned mode and return menu into the dropdown container
    function forceUnpinSidebar() {
        document.documentElement.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
        document.documentElement.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
        if (document.body) {
            document.body.classList.remove('vector-feature-main-menu-pinned-enabled', 'vector-feature-main-menu-pinned-clientpref-1');
            document.body.classList.add('vector-feature-main-menu-pinned-disabled', 'vector-feature-main-menu-pinned-clientpref-0');
        }

        // Clear stored pin preference
        document.cookie = "vector-feature-main-menu-pinned=0; path=/; max-age=31536000";
        try { localStorage.setItem('vector-feature-main-menu-pinned', '0'); } catch(e) {}

        // If #vector-main-menu was moved into the pinned container, move it back into dropdown
        const mainMenu = document.getElementById('vector-main-menu');
        const unpinnedContainer = document.getElementById('vector-main-menu-unpinned-container') || 
                                  document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content');
        const pinnedContainer = document.getElementById('vector-main-menu-pinned-container');

        if (mainMenu && unpinnedContainer && pinnedContainer && pinnedContainer.contains(mainMenu)) {
            unpinnedContainer.appendChild(mainMenu);
        }
    }

    function getDropdownElement() {
        return document.querySelector('#vector-main-menu-dropdown .vector-dropdown-content') || 
               document.getElementById('vector-main-menu-dropdown-content') ||
               document.getElementById('vector-main-menu-unpinned-container') ||
               document.getElementById('vector-main-menu');
    }

    function openMenu(menuBtn) {
        forceUnpinSidebar();

        const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
        const dropdown = getDropdownElement();
        if (!dropdown) return;

        if (checkbox) {
            checkbox.checked = true;
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
        }

        const rect = menuBtn.getBoundingClientRect();
        dropdown.style.setProperty('position', 'fixed', 'important');
        dropdown.style.setProperty('top', rect.bottom + 'px', 'important');
        dropdown.style.setProperty('left', rect.left + 'px', 'important');
        dropdown.style.setProperty('z-index', '99999', 'important');
        dropdown.style.setProperty('max-height', (window.innerHeight - rect.bottom - 16) + 'px', 'important');
        dropdown.style.setProperty('overflow-y', 'auto', 'important');
        dropdown.style.setProperty('display', 'block', 'important');
        dropdown.style.setProperty('visibility', 'visible', 'important');
        dropdown.style.setProperty('opacity', '1', 'important');
        dropdown.style.setProperty('background-color', 'var(--background-color-base, #ffffff)', 'important');
        dropdown.style.setProperty('box-shadow', '0 4px 16px rgba(0, 0, 0, 0.2)', 'important');
        dropdown.style.setProperty('border-radius', '4px', 'important');

        isMenuOpen = true;
    }

    function closeMenu() {
        const checkbox = document.getElementById('vector-main-menu-dropdown-checkbox');
        const dropdown = getDropdownElement();
        if (!dropdown) return;

        if (checkbox) {
            checkbox.checked = false;
            checkbox.dispatchEvent(new Event('change', { bubbles: true }));
        }

        dropdown.style.removeProperty('position');
        dropdown.style.removeProperty('top');
        dropdown.style.removeProperty('left');
        dropdown.style.removeProperty('z-index');
        dropdown.style.removeProperty('max-height');
        dropdown.style.removeProperty('overflow-y');
        dropdown.style.removeProperty('background-color');
        dropdown.style.removeProperty('box-shadow');
        dropdown.style.removeProperty('border-radius');
        dropdown.style.setProperty('display', 'none', 'important');
        dropdown.style.setProperty('visibility', 'hidden', 'important');
        dropdown.style.setProperty('opacity', '0', 'important');

        isMenuOpen = false;
    }

    function initStickyBurger() {
        forceUnpinSidebar();

        const stickyHeader = document.getElementById('vector-sticky-header');
        if (!stickyHeader) return false;
        if (document.getElementById('vanipedia-sticky-main-menu')) return true;

        // 1. Create burger button
        const menuBtn = document.createElement('div');
        menuBtn.id = 'vanipedia-sticky-main-menu';
        menuBtn.title = 'Main menu';
        menuBtn.style.cursor = 'pointer'; 
        menuBtn.className = 'vector-dropdown-label cdx-button cdx-button--fake-button cdx-button--icon-only';

        // 2. Add hamburger icon
        const iconSpan = document.createElement('span');
        iconSpan.className = 'vector-icon mw-ui-icon-menu mw-ui-icon-wikimedia-menu'; 
        menuBtn.appendChild(iconSpan);

        // 3. Toggle click handler (1st click opens, 2nd click rolls back)
        menuBtn.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();

            if (isMenuOpen) {
                closeMenu();
            } else {
                openMenu(menuBtn);
            }
        });

        // 4. Inject into start container
        let startContainer = stickyHeader.querySelector('.vector-sticky-header-start');
        if (!startContainer) {
            startContainer = document.createElement('div');
            startContainer.className = 'vector-sticky-header-start';
            stickyHeader.insertBefore(startContainer, stickyHeader.firstChild);
        }
        
        startContainer.insertBefore(menuBtn, startContainer.firstChild);
        return true;
    }

    // 5. Close when clicking anywhere outside
    document.addEventListener('click', function (e) {
        if (!isMenuOpen) return;
        const menuBtn = document.getElementById('vanipedia-sticky-main-menu');
        const dropdown = getDropdownElement();
        if (dropdown && menuBtn && !menuBtn.contains(e.target) && !dropdown.contains(e.target)) {
            closeMenu();
        }
    });

    // 6. Bootstrap initializer
    if (!initStickyBurger()) {
        const checkTimer = setInterval(function () {
            if (initStickyBurger()) {
                clearInterval(checkTimer);
            }
        }, 100);
        setTimeout(function () { clearInterval(checkTimer); }, 4000);
    }

    mw.hook('wikipage.content').add(initStickyBurger);
    $(initStickyBurger);
})();

/* Floating "Back to Top" Button Controller */
(function () {
    function initBackToTop() {
        if (document.getElementById('vanipedia-back-to-top')) return;

        // 1. Create button element with an up-arrow SVG icon
        const btn = document.createElement('button');
        btn.id = 'vanipedia-back-to-top';
        btn.title = 'Back to top';
        btn.setAttribute('aria-label', 'Back to top');
        
        btn.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
                <polyline points="18 15 12 9 6 15"></polyline>
            </svg>
        `;

        // 2. Smooth scroll to top on click
        btn.addEventListener('click', function (e) {
            e.preventDefault();
            window.scrollTo({ top: 0, behavior: 'smooth' });
        });

        // 3. Show button only after scrolling down 300px
        function toggleVisibility() {
            if (window.scrollY > 300) {
                btn.classList.add('visible');
            } else {
                btn.classList.remove('visible');
            }
        }

        window.addEventListener('scroll', toggleVisibility, { passive: true });
        toggleVisibility();

        document.body.appendChild(btn);
    }

    $(initBackToTop);
    mw.hook('wikipage.content').add(initBackToTop);
})();

(function () {

    if (!document.getElementById('vani-home')) {
        return;
    }

/* =====================================================
   ROTATING HERO PHOTOGRAPHS
   ===================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const vaniHeroImages = [
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-01.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-02.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-03.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-04.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-05.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-06.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-07.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-08.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-09.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-10.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-11.jpg',
        '/wiki/Special:Redirect/file/Srila-Prabhupada-Hero-12.jpg'
    ];

    const heroA = document.querySelector('.vh-photo-a');
    const heroB = document.querySelector('.vh-photo-b');

    if (heroA && heroB && vaniHeroImages.length) {

        let currentImage = -1;
        let activeLayer = heroA;

        function chooseHeroImage() {

            let next;

            do {
                next = Math.floor(
                    Math.random() * vaniHeroImages.length
                );
            } while (
                next === currentImage &&
                vaniHeroImages.length > 1
            );

            return next;
        }

        function changeHero(firstLoad) {

            const next = chooseHeroImage();
            const image = vaniHeroImages[next];

            if (firstLoad) {

                heroA.style.backgroundImage =
                    'url("' + image + '")';

                heroA.style.opacity = '1';
                heroB.style.opacity = '0';

                activeLayer = heroA;
                currentImage = next;

                return;
            }

            const incoming =
                activeLayer === heroA ? heroB : heroA;

            incoming.style.backgroundImage =
                'url("' + image + '")';

            incoming.style.opacity = '1';
            activeLayer.style.opacity = '0';

            activeLayer = incoming;
            currentImage = next;
        }

        changeHero(true);

        window.setInterval(function () {
            changeHero(false);
        }, 10000);
    }
}

/* =====================================================
   ROTATING ŚRĪLA PRABHUPĀDA SUBTITLE
   ===================================================== */

const vaniSubtitles = [
'His words still speak to us today.',
'His vāṇī brings closer to Kṛṣṇa.<br>His vision gives direction in lives.',
'Words spoken decades ago.<br>Wisdom that speaks personally today.',
'His vāṇī lives when we hear it and share with others.',
'There is always more to discover.<br>More to hear. More to understand. More to live.',
'His teachings give direction.<br>His example gives courage to follow.',
'From his heart —<br>a treasure of teachings for every generation.',
'His vāṇī. His compassion. His example.',
'His vāṇī lives on. His teachings guide us.<br>His mission continues.',
'His words cross decades and continents —<br>still reaching hearts, still changing lives.',
'His words continue to speak.<br>His teachings continue to guide.',
'He gave us a great treasure.<br>Vanipedia invites you to enter it.',
'A lifetime of teachings.<br>Invitation to hear him with an open heart.',
'His words illuminate the path.<br>His example inspires us to walk it.',
'He carried Kṛṣṇa consciousness across the world.<br>His vāṇī continues that journey today.',
'He lives on through his words,<br>his teachings and the mission he gave us.',
'His words continue to speak.<br>His teachings continue to guide.',
'Preserving and sharing Śrīla Prabhupāda’s vāṇī<br>so that generations may continue to hear from him.'
];

const vaniSubtitle =
    document.getElementById('vh-subtitle-rotation');

if (vaniSubtitle) {

    let currentSubtitle = -1;

    function chooseSubtitle() {

        let next;

        do {
            next = Math.floor(
                Math.random() * vaniSubtitles.length
            );
        } while (
            next === currentSubtitle &&
            vaniSubtitles.length > 1
        );

        return next;
    }

function showSubtitle(firstLoad) {

    const next = chooseSubtitle();

    if (firstLoad) {
        currentSubtitle = next;
        vaniSubtitle.innerHTML = vaniSubtitles[next];
        vaniSubtitle.classList.remove('is-changing');
        return;
    }

    // Fade out
    vaniSubtitle.classList.add('is-changing');

    window.setTimeout(function () {

        vaniSubtitle.innerHTML = vaniSubtitles[next];
        currentSubtitle = next;

        requestAnimationFrame(function () {
            requestAnimationFrame(function () {
                vaniSubtitle.classList.remove('is-changing');
            });
        });

    }, 450);
}

    /* Initial sentence */
    showSubtitle(true);

    /* Change every 10 seconds */
    window.setInterval(function () {

        showSubtitle(false);

    }, 12000);
}


/* ==========================================================
   MAIN PAGE — REMOVE NATIVE LINK TOOLTIPS
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    function removeMainPageLinkTitles() {

        document
            .querySelectorAll('#vani-home a[title]')
            .forEach(function (link) {

                link.removeAttribute('title');

            });
    }

    $(removeMainPageLinkTitles);

    mw.hook('wikipage.content').add(function () {

        removeMainPageLinkTitles();

    });
}

/* =====================================================
   RANDOM THEMATIC ARTICLE SPOTLIGHT
   ===================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const discoverBox = document.getElementById('vh-random-article');

    if (discoverBox) {

        let articleLinks = [];
        let currentHref = null;

        const sourceUrl =
            mw.util.getUrl('Category:Vanipedia Gemini - Articles');

        function loadArticleLinks() {

            return fetch(sourceUrl)
                .then(function (response) {
                    return response.text();
                })
                .then(function (html) {

                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');

                    const links = Array.from(
                        doc.querySelectorAll('#mw-content-text a')
                    );

                    articleLinks = links
                        .map(function (a) {
                            return {
                                title: a.textContent.trim(),
                                href: a.getAttribute('href')
                            };
                        })
                        .filter(function (item) {

                            if (!item.title || !item.href) {
                                return false;
                            }

                            if (!item.href.startsWith('/wiki/')) {
                                return false;
                            }

                            if (item.href.includes('Category:')) {
                                return false;
                            }

                            if (item.href.includes('Special:')) {
                                return false;
                            }

                            return true;
                        });

                    console.log(
                        'Spotlight links loaded:',
                        articleLinks.length
                    );
                });
        }

        function chooseRandomArticle() {

            if (!articleLinks.length) {
                return null;
            }

            let chosen;

            do {
                chosen =
                    articleLinks[
                        Math.floor(Math.random() * articleLinks.length)
                    ];
            } while (
                chosen.href === currentHref &&
                articleLinks.length > 1
            );

            return chosen;
        }

function getShortTitle(youtubeUrl) {

    const url =
        'https://www.youtube.com/oembed' +
        '?url=' +
        encodeURIComponent(youtubeUrl) +
        '&format=json';

    return fetch(url)
        .then(function(response) {

            if (!response.ok) {
                throw new Error(
                    'YouTube title HTTP ' +
                    response.status
                );
            }

            return response.json();
        })
        .then(function(data) {

            return (
                data &&
                data.title
                    ? data.title.trim()
                    : ''
            );

        })
        .catch(function(error) {

            console.warn(
                'Could not load Short title:',
                error
            );

            return '';
        });
}

 function showRandomArticle() {

    const article = chooseRandomArticle();

    if (!article) {
        return;
    }

    currentHref = article.href;

    const title =
        decodeURIComponent(
            article.href
                .replace(/^\/wiki\//, '')
                .replace(/_/g, ' ')
        );

    const apiUrl =
        mw.util.wikiScript('api') +
        '?action=query' +
        '&format=json' +
        '&prop=extracts' +
        '&exintro=1' +
        '&explaintext=1' +
        '&exchars=260' +
        '&titles=' +
        encodeURIComponent(title);

    fetch(apiUrl)
        .then(function (response) {
            return response.json();
        })
        .then(function (data) {

            if (!data.query || !data.query.pages) {
                return;
            }

            const page = Object.values(data.query.pages)[0];

            /* Do not display pages without a real extract */
            if (
                !page ||
                !page.extract ||
                page.extract.trim().length < 40
            ) {
                showRandomArticle();
                return;
            }

            const extract = page.extract.trim();

            /* Fetch is finished. NOW begin the fade. */
            discoverBox.classList.add('is-changing');

            window.setTimeout(function () {

                discoverBox.innerHTML =
                    '<a class="vh-random-article-link" href="' +
                    article.href +
                    '">' +

                    '<div class="vh-random-article-title">' +
                    escapeHtml(article.title) +
                    '</div>' +

                    '<div class="vh-random-article-extract">' +
                    escapeHtml(extract) +
                    '</div>' +

                    '<div class="vh-random-article-more">' +
                    'Read article →' +
                    '</div>' +

                    '</a>';

                requestAnimationFrame(function () {
                    discoverBox.classList.remove('is-changing');
                });

            }, 500);
        })
        .catch(function () {
            /* Simply try another article */
            showRandomArticle();
        });
}

        function escapeHtml(text) {

            const div = document.createElement('div');
            div.textContent = text;

            return div.innerHTML;
        }

        loadArticleLinks()
            .then(function () {

                if (!articleLinks.length) {
                    return;
                }

                showRandomArticle();

                window.setInterval(function () {
                    showRandomArticle();
                }, 11000);
            })
            .catch(function (error) {
                console.error(
                    'Spotlight failed:',
                    error
                );
            });
    }
}

/* =====================================================
   FROM HIS LETTERS
   Random Śrīla Prabhupāda letter from Vanisource
   ===================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const letterBox =
        document.getElementById('vh-random-letter');

    if (letterBox) {

        const vanisourceApi =
            'https://vanisource.org/w/api.php';

        const rootCategory =
            'Category:Letters from Srila Prabhupada - by person';

        let recipientCategories = [];
        let currentLetter = '';
        let letterChanging = false;


        /* -------------------------------------------------
           LOAD RECIPIENT CATEGORIES
           ------------------------------------------------- */

        function loadLetterRecipients(cmcontinue) {

            let url =
                vanisourceApi +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(rootCategory) +
                '&cmtype=subcat' +
                '&cmlimit=500';

            if (cmcontinue) {

                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }

            return fetch(url)

                .then(function (response) {
                    return response.json();
                })

                .then(function (data) {

                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {

                        data.query.categorymembers
                            .forEach(function (item) {

                                if (
                                    item.title &&
                                    item.title.endsWith(
                                        ' - Letters'
                                    )
                                ) {
                                    recipientCategories.push(
                                        item.title
                                    );
                                }

                            });
                    }


                    /* Continue if more than 500 */

                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {

                        return loadLetterRecipients(
                            data.continue.cmcontinue
                        );
                    }
                });
        }


        /* -------------------------------------------------
           CHOOSE RANDOM RECIPIENT
           ------------------------------------------------- */

        function chooseRecipient() {

            if (!recipientCategories.length) {
                return null;
            }

            return recipientCategories[
                Math.floor(
                    Math.random() *
                    recipientCategories.length
                )
            ];
        }


        /* -------------------------------------------------
           GET LETTERS FOR THAT RECIPIENT
           ------------------------------------------------- */

        function getRandomLetter(category) {

            const url =
                vanisourceApi +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(category) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=500';

            return fetch(url)

                .then(function (response) {
                    return response.json();
                })

                .then(function (data) {

                    if (
                        !data.query ||
                        !data.query.categorymembers ||
                        !data.query.categorymembers.length
                    ) {
                        return null;
                    }

                    const letters =
                        data.query.categorymembers;

                    let letter;

                    do {

                        letter =
                            letters[
                                Math.floor(
                                    Math.random() *
                                    letters.length
                                )
                            ];

                    } while (
                        currentLetter &&
                        letter.title === currentLetter &&
                        letters.length > 1
                    );

                    return letter;
                });
        }


        /* -------------------------------------------------
           RECIPIENT NAME
           ------------------------------------------------- */

        function getRecipientName(category) {

            return category
                .replace(/^Category:/, '')
                .replace(/ - Letters$/, '')
                .trim();
        }


        /* -------------------------------------------------
           TRY TO EXTRACT DATE FROM LETTER PAGE TITLE

           Supports dates at beginning such as:
           690101
           740516
           etc.
           ------------------------------------------------- */

        function getLetterDate(title) {

            const match =
                title.match(
                    /(?:^|\/)(\d{2})(\d{2})(\d{2})(?:\D|$)/
                );

            if (!match) {
                return '';
            }

            const yy =
                parseInt(match[1], 10);

            const month =
                parseInt(match[2], 10);

            const day =
                parseInt(match[3], 10);

            if (
                month < 1 ||
                month > 12 ||
                day < 1 ||
                day > 31
            ) {
                return '';
            }

            const year =
                1900 + yy;

            const months = [
                'January',
                'February',
                'March',
                'April',
                'May',
                'June',
                'July',
                'August',
                'September',
                'October',
                'November',
                'December'
            ];

            return (
                day +
                ' ' +
                months[month - 1] +
                ' ' +
                year
            );
        }


        /* -------------------------------------------------
           GET ACTUAL LETTER CONTENT
           ------------------------------------------------- */

        function getLetterPassage(letterTitle) {

            const url =
                vanisourceApi +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(letterTitle) +
                '&prop=text';

            return fetch(url)

                .then(function (response) {
                    return response.json();
                })

                .then(function (data) {

                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return '';
                    }

                    const wrapper =
                        document.createElement('div');

                    wrapper.innerHTML =
                        data.parse.text['*'];


                    /*
                     * Remove things which definitely are
                     * not part of the actual letter.
                     */

                    wrapper.querySelectorAll(
                        'table, .navbox, .toc, .mw-editsection, ' +
                        '.metadata, .noprint, style, script'
                    ).forEach(function (element) {
                        element.remove();
                    });


                    /*
                     * Collect useful paragraphs.
                     */

                    let paragraphs =
                        Array.from(
                            wrapper.querySelectorAll('p')
                        )

                        .map(function (p) {

                            return p.textContent
                                .replace(/\s+/g, ' ')
                                .trim();
                        })

.filter(function (text) {

    if (text.length < 120) {
        return false;
    }

    const lower = text.toLowerCase();

    /* Standard opening/closing formalities */

    if (
        lower.startsWith('dear ') ||
        lower.startsWith('my dear ') ||
        lower.includes('please accept my blessings') ||
        lower.includes('please accept my humble') ||
        lower.includes('i beg to acknowledge receipt') ||
        lower.includes('i am in due receipt') ||
        lower.includes('your ever well-wisher') ||
        lower.includes('hoping this will meet you') ||
        lower.includes('yours in the service') ||
        lower.includes('your servant')
    ) {
        return false;
    }

    /* Addresses / letter headers */

    if (
        lower.includes('iskcon') ||
        lower.includes('international society for krishna consciousness') ||
        lower.includes('founder-acarya') ||
        lower.includes('founder acharya') ||
        lower.includes('a.c. bhaktivedanta swami') ||
        lower.includes('tridandi goswami') ||
        lower.includes('tridandi gosvami') ||
        lower.includes('bhaktivedanta manor') ||
        lower.includes('hare krishna land')
    ) {
        return false;
    }

    /* Looks too much like an address */

    const addressWords = [
        'road',
        'street',
        'avenue',
        'building',
        'apartment',
        'bombay',
        'calcutta',
        'los angeles',
        'new york',
        'london'
    ];

    let addressHits = 0;

    addressWords.forEach(function (word) {
        if (lower.includes(word)) {
            addressHits++;
        }
    });

    if (addressHits >= 2) {
        return false;
    }

    /*
     * Require something resembling actual prose:
     * at least two sentence endings.
     */

    const sentences =
        (text.match(/[.!?](?:\s|$)/g) || []).length;

    if (sentences < 2) {
        return false;
    }

    return true;
});


                    if (!paragraphs.length) {
                        return '';
                    }


                    /*
                     * Prefer reasonably substantial passages.
                     */

                    let preferred =
                        paragraphs.filter(function (text) {

                            return (
text.length >= 180 &&
text.length <= 400
                            );
                        });


                    if (!preferred.length) {
                        preferred = paragraphs;
                    }


                    /*
                     * Random meaningful paragraph from
                     * within the letter.
                     */

                    let passage =
                        preferred[
                            Math.floor(
                                Math.random() *
                                preferred.length
                            )
                        ];


                    /*
                     * Prevent enormous passage in narrow
                     * left column.
                     */

if (passage.length > 300) {

    passage =
        passage.substring(0, 300);

    const lastSpace =
        passage.lastIndexOf(' ');

    if (lastSpace > 250) {
        passage =
            passage.substring(
                0,
                lastSpace
            );
    }

    passage += '…';
}

                    return passage;
                });
        }


        /* -------------------------------------------------
           SAFE HTML
           ------------------------------------------------- */

        function escapeLetterHtml(text) {

            const div =
                document.createElement('div');

            div.textContent = text;

            return div.innerHTML;
        }


        /* -------------------------------------------------
           BUILD VANISOURCE URL
           ------------------------------------------------- */

        function getLetterUrl(title) {

            return (
                'https://vanisource.org/wiki/' +
                encodeURIComponent(
                    title.replace(/ /g, '_')
                )
                    .replace(/%2F/g, '/')
                    .replace(/%3A/g, ':')
            );
        }


        /* -------------------------------------------------
           FIND A VALID RANDOM LETTER
           ------------------------------------------------- */

        function prepareRandomLetter(attempt) {

            attempt = attempt || 0;

            /*
             * Prevent an endless retry loop if Vanisource
             * unexpectedly returns unusable pages.
             */

            if (attempt > 15) {
                return Promise.resolve(null);
            }

            const category =
                chooseRecipient();

            if (!category) {
                return Promise.resolve(null);
            }

            return getRandomLetter(category)

                .then(function (letter) {

                    if (!letter || !letter.title) {

                        return prepareRandomLetter(
                            attempt + 1
                        );
                    }

                    return getLetterPassage(
                        letter.title
                    )

                        .then(function (passage) {

                            if (
                                !passage ||
                                passage.length < 80
                            ) {

                                return prepareRandomLetter(
                                    attempt + 1
                                );
                            }

                            return {

                                title:
                                    letter.title,

                                recipient:
                                    getRecipientName(
                                        category
                                    ),

                                date:
                                    getLetterDate(
                                        letter.title
                                    ),

                                passage:
                                    passage,

                                href:
                                    getLetterUrl(
                                        letter.title
                                    )
                            };
                        });
                });
        }


        /* -------------------------------------------------
           DISPLAY LETTER
           ------------------------------------------------- */

        function showRandomLetter() {

            if (letterChanging) {
                return;
            }

            letterChanging = true;


            /*
             * Prepare the next letter while the current
             * one remains fully visible.
             */

            prepareRandomLetter(0)

                .then(function (letter) {

                    if (!letter) {
                        letterChanging = false;
                        return;
                    }

                    currentLetter =
                        letter.title;


                    /*
                     * Only start fading AFTER the new
                     * content is ready.
                     */

                    letterBox.classList.add(
                        'is-changing'
                    );


                    window.setTimeout(
                        function () {

                            let meta = '';

                            if (letter.date) {

                                meta =
                                    '<div class="vh-random-letter-date">' +
                                    escapeLetterHtml(
                                        letter.date
                                    ) +
                                    '</div>';
                            }


                            letterBox.innerHTML =

                                '<a class="vh-random-letter-link" ' +
                                'href="' +
                                letter.href +
                                '">' +

'<div class="vh-random-letter-person">' +
'To ' +
escapeLetterHtml(
    letter.recipient
) +
'</div>' +

                                meta +

                                '<div class="vh-random-letter-text">' +
                                escapeLetterHtml(
                                    letter.passage
                                ) +
                                '</div>' +

                                '<div class="vh-random-letter-more">' +
                                'Read letter →' +
                                '</div>' +

                                '</a>';


                            requestAnimationFrame(
                                function () {

                                    letterBox.classList.remove(
                                        'is-changing'
                                    );

                                    letterChanging = false;
                                }
                            );

                        },
                        450
                    );
                })

                .catch(function (error) {

                    console.error(
                        'Letter feature failed:',
                        error
                    );

                    letterChanging = false;
                });
        }


        /* -------------------------------------------------
           START
           ------------------------------------------------- */

        loadLetterRecipients()

            .then(function () {

                if (!recipientCategories.length) {

                    console.warn(
                        'No Vanisource letter recipients found.'
                    );

                    return;
                }

                console.log(
                    'Letter recipient categories loaded:',
                    recipientCategories.length
                );


                /*
                 * First letter immediately.
                 */

                showRandomLetter();


                /*
                 * New letter every 10 seconds.
                 */

                window.setInterval(
                    showRandomLetter,
                    10000
                );
            })

            .catch(function (error) {

                console.error(
                    'Could not load Vanisource letters:',
                    error
                );
            });
    }
}

/* ==========================================================
   VIRTUAL CONTINUOUS ŚRĪLA PRABHUPĀDA TIMELINE
   Includes 1896–1964 + 1965–1977
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const timelineWindow =
        document.querySelector('.vh-lifeline-window');

    const timelineTrack =
        document.getElementById('vh-timeline-track');

const yearButtons =
    document.querySelectorAll(
        '#vh-timeline-years .vh-year'
    );

    if (timelineWindow && timelineTrack) {

        const API =
            'https://vanipedia.org/w/api.php';

        const ITEM_WIDTH = 220;

        const AUTO_SPEED = 52;

        const VISIBLE_ITEMS = 22;

        let events = [];

        let nodes = [];

        let firstIndex = 0;

        let offsetX = 0;

        let dragging = false;
        let dragMoved = false;

        let pointerStartX = 0;
        let previousPointerX = 0;

        let lastFrame =
            performance.now();

        let animationId = null;


        /* ==================================================
           TEXT HELPERS
           ================================================== */

        function cleanText(text) {

            return text
                .replace(/\s+/g, ' ')
                .replace(/^[\s\-–—:,]+/, '')
                .trim();
        }


        function shortenText(text) {

            text =
                cleanText(text);

            if (text.length <= 95) {
                return text;
            }

            let result =
                text.substring(0, 95);

            const space =
                result.lastIndexOf(' ');

            if (space > 70) {

                result =
                    result.substring(
                        0,
                        space
                    );
            }

            return result + '…';
        }


        /* ==================================================
           DEFAULT PAGE LINK
           ================================================== */

        function makePageUrl(page) {

            return (
                'https://vanipedia.org/wiki/' +
                encodeURIComponent(
                    page.replace(/ /g, '_')
                )
                .replace(/%2F/g, '/')
                .replace(/%3A/g, ':')
            );
        }


        /* ==================================================
           FIND BEST LINK INSIDE ENTRY
           ================================================== */

        function getUsefulLink(
            element,
            fallback
        ) {

            const usefulLink =
                Array
                    .from(
                        element.querySelectorAll('a')
                    )
                    .find(
                        function (a) {

                            const h =
                                a.getAttribute(
                                    'href'
                                );

                            return (
                                h &&
                                !h.includes(
                                    'action=edit'
                                ) &&
                                !h.includes(
                                    'redlink=1'
                                )
                            );
                        }
                    );


            if (!usefulLink) {
                return fallback;
            }


            const rawHref =
                usefulLink.getAttribute(
                    'href'
                );


            if (!rawHref) {
                return fallback;
            }


            try {

                return new URL(
                    rawHref,
                    'https://vanipedia.org'
                ).href;

            } catch (e) {

                return fallback;
            }
        }


        /* ==================================================
           FETCH 1896–1964
           Main timeline page
           ================================================== */

        function fetchEarlyTimeline() {

            const page =
                "Timeline of Srila Prabhupada's Life";

            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page) +
                '&prop=text';


            return fetch(url)

                .then(function (response) {

                    return response.json();
                })

                .then(function (data) {

                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {

                        return [];
                    }


                    const temp =
                        document.createElement('div');

                    temp.innerHTML =
                        data.parse.text['*'];


                    const result = [];


                    temp
                        .querySelectorAll('li')
                        .forEach(function (li) {

                            const fullText =
                                cleanText(
                                    li.textContent
                                );


                            /*
                             * Expected examples:
                             *
                             * 1896 - September 01 - Calcutta - ...
                             * 1922 - Meets ...
                             * 1944 - February 14 - Publishes ...
                             * 1959 - September 17 - ...
                             *
                             * We only keep 1896–1964.
                             */

                            const match =
                                fullText.match(
                                    /^((?:18|19)\d{2})\s*[-–—]\s*(.+)$/i
                                );


                            if (!match) {
                                return;
                            }


                            const year =
                                parseInt(
                                    match[1],
                                    10
                                );


                            if (
                                year < 1896 ||
                                year > 1964
                            ) {

                                return;
                            }


                            let remainder =
                                cleanText(
                                    match[2]
                                );


                            if (!remainder) {
                                return;
                            }


                            /*
                             * Try to extract a date
                             * from the remainder.
                             */

                            let displayDate =
                                String(year);

                            let description =
                                remainder;


                            const monthMatch =
                                remainder.match(
                                    /^(January|February|March|April|May|June|July|August|September|October|November|December)(?:\s+(\d{1,2}))?\s*[-–—]\s*(.+)$/i
                                );


                            if (monthMatch) {

                                const month =
                                    monthMatch[1];

                                const day =
                                    monthMatch[2]
                                        ? monthMatch[2]
                                        : '';

                                description =
                                    cleanText(
                                        monthMatch[3]
                                    );


                                displayDate =
                                    month;


                                if (day) {

                                    displayDate +=
                                        ' ' +
                                        day;
                                }


                                displayDate +=
                                    ', ' +
                                    year;

                            } else {

                                /*
                                 * If no month is present,
                                 * show year only.
                                 */

                                description =
                                    remainder;
                            }


                            description =
                                shortenText(
                                    description
                                );


                            if (!description) {
                                return;
                            }


                            const fallback =
                                makePageUrl(page);


                            const href =
                                getUsefulLink(
                                    li,
                                    fallback
                                );


                            result.push({

                                year:
                                    year,

                                group:
                                    '1896-1964',

                                date:
                                    displayDate,

                                text:
                                    description,

                                href:
                                    href
                            });
                        });


                    return result;
                });
        }


        /* ==================================================
           FETCH ONE YEAR: 1965–1977
           ================================================== */

        function fetchYear(year) {

            const page =
                "Timeline of Srila Prabhupada's Life - " +
                year;

            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page) +
                '&prop=text';


            return fetch(url)

                .then(function (response) {

                    return response.json();
                })

                .then(function (data) {

                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {

                        return [];
                    }


                    const temp =
                        document.createElement('div');

                    temp.innerHTML =
                        data.parse.text['*'];


                    const result = [];


                    temp
                        .querySelectorAll('li')
                        .forEach(function (li) {

                            const fullText =
                                cleanText(
                                    li.textContent
                                );


                            const match =
                                fullText.match(
                                    /^(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2})(?:,?\s+([0-9]{1,2}(?::[0-9]{2})?(?:\s*[AP]\.?M\.?)?))?\s*[-–—]\s*(.+)$/i
                                );


                            if (!match) {
                                return;
                            }


                            const month =
                                match[1];

                            const day =
                                match[2];

                            const time =
                                match[3]
                                    ? match[3].trim()
                                    : '';

                            const description =
                                shortenText(
                                    match[4]
                                );


                            if (!description) {
                                return;
                            }


                            const fallback =
                                makePageUrl(page);


                            const href =
                                getUsefulLink(
                                    li,
                                    fallback
                                );


                            let date =
                                month +
                                ' ' +
                                day +
                                ', ' +
                                year;


                            if (time) {

                                date +=
                                    ' · ' +
                                    time;
                            }


                            result.push({

                                year:
                                    year,

                                group:
                                    String(year),

                                date:
                                    date,

                                text:
                                    description,

                                href:
                                    href
                            });
                        });


                    return result;
                });
        }


        /* ==================================================
           CREATE NODE
           ================================================== */

        function createNode() {

            const link =
                document.createElement('a');

            link.className =
                'vh-timeline-item';

            link.draggable =
                false;


            const date =
                document.createElement('div');

            date.className =
                'vh-timeline-date';


            const dot =
                document.createElement('span');

            dot.className =
                'vh-timeline-dot';


            const text =
                document.createElement('div');

            text.className =
                'vh-timeline-text';


            link.appendChild(date);
            link.appendChild(dot);
            link.appendChild(text);


            link.addEventListener(
                'click',
                function (event) {

                    if (dragMoved) {

                        event.preventDefault();
                    }
                }
            );


            return link;
        }


        /* ==================================================
           POPULATE NODE
           ================================================== */

        function populateNode(
            node,
            eventIndex
        ) {

            const total =
                events.length;


            eventIndex =
                (
                    (
                        eventIndex %
                        total
                    ) +
                    total
                ) %
                total;


            const item =
                events[eventIndex];


            node.href =
                item.href;


            node.dataset.year =
                item.year;


            node.dataset.group =
                item.group;


            node
                .querySelector(
                    '.vh-timeline-date'
                )
                .textContent =
                    item.date;


            node
                .querySelector(
                    '.vh-timeline-text'
                )
                .textContent =
                    item.text;
        }


        /* ==================================================
           BUILD VIRTUAL STRIP
           ================================================== */

        function buildVirtualTimeline() {

            timelineTrack.innerHTML = '';

            nodes = [];


            const needed =
                Math.max(
                    VISIBLE_ITEMS,
                    Math.ceil(
                        timelineWindow.clientWidth /
                        ITEM_WIDTH
                    ) + 6
                );


            for (
                let i = 0;
                i < needed;
                i++
            ) {

                const node =
                    createNode();


                populateNode(
                    node,
                    firstIndex + i
                );


                timelineTrack.appendChild(
                    node
                );


                nodes.push(
                    node
                );
            }


            timelineTrack.style.width =
                (
                    needed *
                    ITEM_WIDTH
                ) +
                'px';


            offsetX =
                -ITEM_WIDTH;


            firstIndex =
                (
                    firstIndex -
                    1 +
                    events.length
                ) %
                events.length;


            refreshAllNodes();

            draw();
        }


        function refreshAllNodes() {

            nodes.forEach(
                function (
                    node,
                    position
                ) {

                    populateNode(
                        node,
                        firstIndex +
                        position
                    );
                }
            );
        }


        /* ==================================================
           RECYCLE LEFT
           ================================================== */

        function recycleLeft() {

            const node =
                nodes.shift();


            nodes.push(
                node
            );


            timelineTrack.appendChild(
                node
            );


            firstIndex =
                (
                    firstIndex +
                    1
                ) %
                events.length;


            populateNode(
                node,
                firstIndex +
                nodes.length -
                1
            );


            offsetX +=
                ITEM_WIDTH;
        }


        /* ==================================================
           RECYCLE RIGHT
           ================================================== */

        function recycleRight() {

            const node =
                nodes.pop();


            firstIndex =
                (
                    firstIndex -
                    1 +
                    events.length
                ) %
                events.length;


            nodes.unshift(
                node
            );


            timelineTrack.insertBefore(
                node,
                timelineTrack.firstChild
            );


            populateNode(
                node,
                firstIndex
            );


            offsetX -=
                ITEM_WIDTH;
        }


        function normalizePosition() {

            while (
                offsetX <=
                -ITEM_WIDTH * 2
            ) {

                recycleLeft();
            }


            while (
                offsetX > 0
            ) {

                recycleRight();
            }
        }


        /* ==================================================
           DRAW
           ================================================== */

        function draw() {

            timelineTrack.style.transform =
                'translate3d(' +
                offsetX +
                'px,0,0)';
        }


        /* ==================================================
           ACTIVE YEAR BUTTON
           ================================================== */

function setActiveButton(group) {

    yearButtons.forEach(
        function (button) {

            const value =
                button.classList.contains(
                    'vh-year-early'
                )
                    ? '1896-1964'
                    : button.textContent.trim();

            button.classList.toggle(
                'is-active',
                value === group
            );
        }
    );
}

        /* ==================================================
           JUMP TO YEAR / PERIOD
           ================================================== */

        function jumpToGroup(group) {

            if (!events.length) {
                return;
            }


            let targetIndex;


            if (group === '1896-1964') {

                targetIndex =
                    events.findIndex(
                        function (item) {

                            return (
                                item.group ===
                                '1896-1964'
                            );
                        }
                    );

            } else {

                const year =
                    parseInt(
                        group,
                        10
                    );


                targetIndex =
                    events.findIndex(
                        function (item) {

                            return (
                                item.year ===
                                year
                            );
                        }
                    );
            }


            if (targetIndex === -1) {
                return;
            }


            /*
             * Put target near left side,
             * with one virtual buffer item before it.
             */

            firstIndex =
                (
                    targetIndex -
                    1 +
                    events.length
                ) %
                events.length;


            offsetX =
                -ITEM_WIDTH;


            refreshAllNodes();

            draw();


            setActiveButton(
                group
            );
        }


/* ==================================================
   YEAR NAVIGATION
   ================================================== */

yearButtons.forEach(
    function (button) {

        button.addEventListener(
            'click',
            function () {

                const value =
                    button.classList.contains(
                        'vh-year-early'
                    )
                        ? '1896-1964'
                        : button.textContent.trim();

                jumpToGroup(value);
            }
        );
    }
);

        /* ==================================================
           ANIMATION
           ================================================== */

        function animate(now) {

            let elapsed =
                (
                    now -
                    lastFrame
                ) /
                1000;


            lastFrame =
                now;


            if (elapsed > 0.05) {

                elapsed =
                    0.05;
            }


            if (!dragging) {

                offsetX -=
                    AUTO_SPEED *
                    elapsed;


                normalizePosition();

                draw();
            }


            animationId =
                requestAnimationFrame(
                    animate
                );
        }


        /* ==================================================
           DRAGGING
           ================================================== */

        timelineWindow.addEventListener(
            'pointerdown',
            function (event) {

                if (
                    event.pointerType ===
                        'mouse' &&
                    event.button !== 0
                ) {

                    return;
                }


                dragging =
                    true;

                dragMoved =
                    false;


                pointerStartX =
                    event.clientX;

                previousPointerX =
                    event.clientX;


                timelineWindow.classList.add(
                    'is-dragging'
                );


                timelineWindow.setPointerCapture(
                    event.pointerId
                );


                event.preventDefault();
            }
        );


        timelineWindow.addEventListener(
            'pointermove',
            function (event) {

                if (!dragging) {
                    return;
                }


                const currentX =
                    event.clientX;


                const difference =
                    currentX -
                    previousPointerX;


                previousPointerX =
                    currentX;


                if (
                    Math.abs(
                        currentX -
                        pointerStartX
                    ) > 4
                ) {

                    dragMoved =
                        true;
                }


                offsetX +=
                    difference;


                normalizePosition();

                draw();


                event.preventDefault();
            }
        );


        function endDrag(event) {

            if (!dragging) {
                return;
            }


            dragging =
                false;


            timelineWindow.classList.remove(
                'is-dragging'
            );


            try {

                if (
                    timelineWindow.hasPointerCapture(
                        event.pointerId
                    )
                ) {

                    timelineWindow.releasePointerCapture(
                        event.pointerId
                    );
                }

            } catch (e) {}


            if (dragMoved) {

                window.setTimeout(
                    function () {

                        dragMoved =
                            false;

                    },
                    180
                );
            }
        }


        timelineWindow.addEventListener(
            'pointerup',
            endDrag
        );


        timelineWindow.addEventListener(
            'pointercancel',
            endDrag
        );


        timelineWindow.addEventListener(
            'dragstart',
            function (event) {

                event.preventDefault();
            }
        );


        /* ==================================================
           RESIZE
           ================================================== */

        let resizeTimer;


        window.addEventListener(
            'resize',
            function () {

                clearTimeout(
                    resizeTimer
                );


                resizeTimer =
                    setTimeout(
                        function () {

                            if (
                                events.length
                            ) {

                                buildVirtualTimeline();
                            }

                        },
                        150
                    );
            }
        );


        /* ==================================================
           LOAD ALL TIMELINE DATA
           ================================================== */

        const requests = [];

        /*
         * First: 1896–1964
         */
        requests.push(
            fetchEarlyTimeline()
        );


        /*
         * Then: 1965–1977
         */
        for (
            let year = 1965;
            year <= 1977;
            year++
        ) {

            requests.push(
                fetchYear(year)
            );
        }


        Promise
            .all(requests)

            .then(
                function (results) {

                    events =
                        results.flat();


                    /*
                     * Ensure chronological order
                     * between groups.
                     */

                    events.sort(
                        function (a, b) {

                            return (
                                a.year -
                                b.year
                            );
                        }
                    );


                    console.log(
                        'TIMELINE EVENTS:',
                        events.length
                    );


                    console.log(
                        'EARLY EVENTS:',
                        events.filter(
                            function (item) {

                                return (
                                    item.group ===
                                    '1896-1964'
                                );
                            }
                        ).length
                    );


                    if (!events.length) {

                        console.warn(
                            'No timeline events found.'
                        );

                        return;
                    }


                    /*
                     * Start with 1965.
                     */

                    const startIndex =
                        events.findIndex(
                            function (item) {

                                return (
                                    item.year ===
                                    1965
                                );
                            }
                        );


                    firstIndex =
                        startIndex >= 0
                            ? startIndex
                            : 0;


                    buildVirtualTimeline();


                    setActiveButton(
                        '1965'
                    );


                    lastFrame =
                        performance.now();


                    animationId =
                        requestAnimationFrame(
                            animate
                        );
                }
            )

            .catch(
                function (error) {

                    console.error(
                        'Timeline loading failed:',
                        error
                    );
                }
            );
    }
}

/* =========================================================
   RANDOM NECTAR DROP — HOMEPAGE QUOTE
   ========================================================= */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const quoteWrap =
        document.getElementById('vh-random-quote-wrap');

    const quoteText =
        document.getElementById('vh-random-quote');

    const quoteSource =
        document.getElementById('vh-random-quote-source');

    if (quoteWrap && quoteText && quoteSource) {

        const API =
            'https://vanipedia.org/w/api.php';

        const CATEGORY =
            'Category:Nectar Drops from Srila Prabhupada';

        let nectarPages = [];
        let quoteChanging = false;
        let currentPage = '';


        /* -----------------------------------------
           LOAD ALL NECTAR DROP PAGES
           ----------------------------------------- */

        function loadNectarPages(cmcontinue) {

            let url =
                API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(CATEGORY) +
                '&cmnamespace=0' +
                '&cmlimit=max';

            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }

            return fetch(url)
                .then(function(response) {

                    if (!response.ok) {
                        throw new Error(
                            'HTTP ' + response.status
                        );
                    }

                    return response.json();
                })
                .then(function(data) {

                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {
                        nectarPages =
                            nectarPages.concat(
                                data.query.categorymembers
                            );
                    }

                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {
                        return loadNectarPages(
                            data.continue.cmcontinue
                        );
                    }

                    console.log(
                        'Nectar Drop pages loaded:',
                        nectarPages.length
                    );
                });
        }


        /* -----------------------------------------
           GET RANDOM NECTAR DROP
           ----------------------------------------- */

        function getRandomNectarDrop() {

            if (
                quoteChanging ||
                !nectarPages.length
            ) {
                return;
            }

            quoteChanging = true;

            let page;

            for (
                let attempt = 0;
                attempt < 20;
                attempt++
            ) {

                const candidate =
                    nectarPages[
                        Math.floor(
                            Math.random() *
                            nectarPages.length
                        )
                    ];

                if (
                    candidate &&
                    candidate.title !== currentPage
                ) {
                    page = candidate;
                    break;
                }
            }

            if (!page) {
                quoteChanging = false;
                return;
            }


            /* -------------------------------------
               GET RAW WIKITEXT
               ------------------------------------- */

            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page.title) +
                '&prop=wikitext';

            fetch(url)
                .then(function(response) {

                    if (!response.ok) {
                        throw new Error(
                            'HTTP ' +
                            response.status
                        );
                    }

                    return response.json();
                })
                .then(function(data) {

                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        throw new Error(
                            'No wikitext returned.'
                        );
                    }

                    const wikitext =
                        data.parse.wikitext['*'];


                    /* ---------------------------------
                       FIND AUDIOBOX TEMPLATE
                       --------------------------------- */

                    const start =
                        wikitext.indexOf(
                            '{{Audiobox_NDrops|'
                        );

                    if (start === -1) {

                        quoteChanging = false;

                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );

                        return;
                    }


                    const end =
                        wikitext.indexOf(
                            '}}',
                            start
                        );

                    if (end === -1) {

                        quoteChanging = false;

                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );

                        return;
                    }


                    const template =
                        wikitext.substring(
                            start +
                            '{{Audiobox_NDrops|'.length,
                            end
                        );


                    /* ---------------------------------
                       SPLIT TEMPLATE PARAMETERS
                       --------------------------------- */

                    const parts =
                        template.split('|');

                    if (parts.length < 5) {

                        quoteChanging = false;

                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );

                        return;
                    }


                    /*
                     * Parameter structure:
                     *
                     * 0 = Nectar Drops from...
                     * 1 = mp3 player
                     * 2 = QUOTE
                     * 3 = Vanisource page
                     * 4 = source label
                     */

                    let quote =
                        parts[2]
                            .trim()
                            .replace(
                                /^["“]+/,
                                ''
                            )
                            .replace(
                                /["”]+$/,
                                ''
                            )
                            .trim();

 const vanisourcePage =
    parts[3]
        .trim()

        /* remove wiki-link brackets */
        .replace(/^\[\[/, '')
        .replace(/\]\]$/, '')

        /* if link contains |display text, keep only target */
        .split('|')[0]

        /* remove namespace prefix */
        .replace(/^Vanisource:/i, '')

        /* remove any remaining stray brackets/braces */
        .replace(/[\[\]\{\}]/g, '')

        .trim();

                    const sourceLabel =
                        parts[4]
                            .trim();


                    if (!quote) {

                        quoteChanging = false;

                        window.setTimeout(
                            getRandomNectarDrop,
                            250
                        );

                        return;
                    }


                    /* ---------------------------------
                       CLEAN SIMPLE WIKITEXT
                       --------------------------------- */

                    quote =
                        quote
                            .replace(
                                /'''?/g,
                                ''
                            )
                            .replace(
                                /\[\[([^|\]]+)\|([^\]]+)\]\]/g,
                                '$2'
                            )
                            .replace(
                                /\[\[([^\]]+)\]\]/g,
                                '$1'
                            )
                            .replace(
                                /\s+/g,
                                ' '
                            )
                            .trim();


                    /* ---------------------------------
                       SOURCE LINK
                       --------------------------------- */

const href =
    'https://vanisource.org/wiki/' +
    vanisourcePage
        .replace(/ /g, '_')
        .split('/')
        .map(encodeURIComponent)
        .join('/');

/* ---------------------------------
   DISPLAY
   --------------------------------- */

currentPage = page.title;

quoteText.classList.add(
    'is-changing'
);

quoteSource.classList.add(
    'is-changing'
);

window.setTimeout(
    function() {

const cleanQuote =
    quote
        .replace(
            /\s*\(?\[\[Vanisource:.*$/i,
            ''
        )
        .trim();

quoteText.textContent =
    cleanQuote;

        quoteSource.innerHTML =
            '';

        const link =
            document.createElement('a');

        link.href =
            href;

let cleanSource = page.title
    .replace(/^\d{6}[a-z]?\s*-\s*/i, '')
    .replace(
        /\s*-\s*Srila Prabhupada Speaks a Nectar Drop in\s*/i,
        ', '
    )
    .trim();

link.textContent =
    cleanSource;

        quoteSource.appendChild(
            link
        );

        /*
         * Reveal only after the first
         * random quote is ready.
         */
        quoteWrap.style.visibility =
            'visible';

        requestAnimationFrame(
            function() {

                quoteText.classList.remove(
                    'is-changing'
                );

                quoteSource.classList.remove(
                    'is-changing'
                );

                quoteChanging = false;
            }
        );

    },
    450
);

                })
                .catch(function(error) {

                    console.error(
                        'Random Nectar Drop failed:',
                        error
                    );

                    quoteChanging = false;
                });
        }


        /* -----------------------------------------
           START
           ----------------------------------------- */

        loadNectarPages()
            .then(function() {

                if (nectarPages.length) {

                    getRandomNectarDrop();

                    window.setInterval(
                        getRandomNectarDrop,
                        9000
                    );
                }
            })
            .catch(function(error) {

                console.error(
                    'Nectar Drop category failed:',
                    error
                );
            });

    }
}

/* Vani Treasure (Featured) */

/* ==========================================================
   HOMEPAGE — RANDOM SHORT
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const shortCard =
        document.getElementById('vh-random-short');

    if (shortCard) {

        const API =
            'https://vanipedia.org/w/api.php';

        const SHORTS_PAGE =
            'VanimediaMayapur YouTube SHORTS List';

        let shortItems = [];
        let currentShort = -1;


        /* --------------------------------------------------
           LOAD SHORTS LIST
           -------------------------------------------------- */

        function loadShorts() {

            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(SHORTS_PAGE) +
                '&prop=wikitext';

            fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {

                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        throw new Error(
                            'Shorts wikitext not found'
                        );
                    }

                    const wiki =
                        data.parse.wikitext['*'];


                    /*
                     * Example:
                     *
                     * File:Short_1139.png
                     * |1139 - 21 August
                     * |link=https://www.youtube.com/shorts/_Wumpw8SAYs
                     */

                    const regex =
                        /File:(Short_(\d+)\.(?:png|jpg|jpeg|webp))\s*\|\s*(\d+)\s*-\s*([^|\n]+?)\s*\|\s*link=(https:\/\/www\.youtube\.com\/shorts\/[^\s|\n]+)/gi;

                    let match;

                    while (
                        (match = regex.exec(wiki)) !== null
                    ) {

                        shortItems.push({
                            file: match[1],
                            number: match[3],
                            date: match[4].trim(),
                            youtube: match[5].trim()
                        });

                    }


                    console.log(
                        'Homepage Shorts loaded:',
                        shortItems.length
                    );


                    if (shortItems.length) {
                        showRandomShort();
                    }

                })
                .catch(function(error) {

                    console.error(
                        'Homepage Shorts failed:',
                        error
                    );

                });
        }


        /* --------------------------------------------------
           GET ACTUAL THUMBNAIL URL
           -------------------------------------------------- */

        function getShortThumbnail(fileName) {

            const url =
                API +
                '?action=query' +
                '&format=json' +
                '&prop=imageinfo' +
                '&iiprop=url' +
                '&iiurlwidth=400' +
                '&titles=' +
                encodeURIComponent(
                    'File:' + fileName
                );

            return fetch(url)
                .then(function(response) {
                    return response.json();
                })
                .then(function(data) {

                    if (
                        !data.query ||
                        !data.query.pages
                    ) {
                        return '';
                    }

                    const pages =
                        Object.values(
                            data.query.pages
                        );

                    if (
                        !pages.length ||
                        !pages[0].imageinfo ||
                        !pages[0].imageinfo.length
                    ) {
                        return '';
                    }

                    const info =
                        pages[0].imageinfo[0];

                    return (
                        info.thumburl ||
                        info.url ||
                        ''
                    );

                });
        }


        /* --------------------------------------------------
           DISPLAY RANDOM SHORT
           -------------------------------------------------- */

        function showRandomShort() {

            if (!shortItems.length) {
                return;
            }

            let index;

            do {

                index =
                    Math.floor(
                        Math.random() *
                        shortItems.length
                    );

            }
            while (
                shortItems.length > 1 &&
                index === currentShort
            );


            currentShort = index;

            const item =
                shortItems[index];

            const media =
                shortCard.querySelector(
                    '.vh-feature-media'
                );

            const copy =
                shortCard.querySelector(
                    '.vh-feature-copy'
                );

            if (!media || !copy) {
                return;
            }


            /* ---------- thumbnail ---------- */

getShortThumbnail(item.file)
    .then(function(imageUrl) {

        if (!imageUrl) {
            return;
        }

        const preload = new Image();

        preload.onload = function() {

            media.style.backgroundImage =
                'url("' + imageUrl + '")';

        };

        preload.src = imageUrl;
    });

            /* ---------- text ---------- */

            copy.innerHTML = '';

            const type =
                document.createElement('small');

            type.textContent =
                'SHORT';


const title =
    document.createElement('strong');

title.textContent =
    'Loading title…';

            copy.appendChild(type);
            copy.appendChild(title);

getShortTitle(item.youtube)
    .then(function(actualTitle) {

        /*
         * Make sure this card has not already
         * rotated to another Short.
         */

        if (
            shortItems[currentShort] !== item
        ) {
            return;
        }

let cleanTitle = actualTitle
    .replace(/\s*-\s*Prabhupada.*$/i, '')
    .trim();

title.textContent =
    cleanTitle ||
    item.number;
    });

            /* ---------- click ---------- */

            shortCard.onclick =
                function() {

                    window.open(
                        item.youtube,
                        '_blank',
                        'noopener'
                    );

                };
        }


        /* --------------------------------------------------
           START
           -------------------------------------------------- */

        loadShorts();


        /*
         * Temporary individual rotation.
         *
         * Later we'll replace this with the common
         * staggered controller for all four cards.
         */

        window.setInterval(
            function() {

                if (shortItems.length) {
                    showRandomShort();
                }

            },
            10500
        );

    }
}

/* ==========================================================
   HOMEPAGE — RANDOM VIDEO
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const videoCard =
        document.getElementById('vh-random-video');

    if (videoCard) {

        const API =
            'https://vanipedia.org/w/api.php';

        const CATEGORY =
            'Category:1080 English Pages with Videos';

        let videoPages = [];
        let currentVideo = '';


        /* --------------------------------------------------
           LOAD VIDEO PAGES
           -------------------------------------------------- */

        function loadVideoPages(cmcontinue) {

            let url =
                API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(CATEGORY) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=max';

            if (cmcontinue) {
                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }

            return fetch(url)

                .then(function(response) {
                    return response.json();
                })

                .then(function(data) {

                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {

                        data.query.categorymembers
                            .forEach(function(page) {

                                if (
                                    /^Prabhupada \d{4}\s*-/i
                                        .test(page.title)
                                ) {
                                    videoPages.push(page);
                                }
                            });
                    }

                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {

                        return loadVideoPages(
                            data.continue.cmcontinue
                        );
                    }
                });
        }


        /* --------------------------------------------------
           CHOOSE RANDOM VIDEO
           -------------------------------------------------- */

        function chooseRandomVideoPage() {

            if (!videoPages.length) {
                return null;
            }

            let page;

            do {

                page =
                    videoPages[
                        Math.floor(
                            Math.random() *
                            videoPages.length
                        )
                    ];

            } while (
                videoPages.length > 1 &&
                page.title === currentVideo
            );

            return page;
        }


        /* --------------------------------------------------
           CLEAN TITLE
           -------------------------------------------------- */

        function getVideoDisplayTitle(pageTitle) {

            return pageTitle
                .replace(
                    /^Prabhupada\s+\d{4}\s*-\s*/i,
                    ''
                )
                .trim();
        }


        /* --------------------------------------------------
           VANIPEDIA PAGE URL
           -------------------------------------------------- */

        function getVideoPageUrl(pageTitle) {

            return (
                'https://vanipedia.org/wiki/' +
                encodeURIComponent(
                    pageTitle.replace(/ /g, '_')
                )
                    .replace(/%2F/g, '/')
                    .replace(/%3A/g, ':')
            );
        }


        /* --------------------------------------------------
           GET YOUTUBE ID FROM PAGE WIKITEXT
           -------------------------------------------------- */

        function loadVideoInfo(page) {

            const url =
                API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(page.title) +
                '&prop=wikitext';

            return fetch(url)

                .then(function(response) {
                    return response.json();
                })

                .then(function(data) {

                    if (
                        !data.parse ||
                        !data.parse.wikitext ||
                        !data.parse.wikitext['*']
                    ) {
                        return null;
                    }

                    const wiki =
                        data.parse.wikitext['*'];

                    /*
                     * Example:
                     * {{youtube_right|rEc1U_lsJ7s|Expand to Ten Million...}}
                     */

                    const match =
                        wiki.match(
                            /\{\{youtube_right\s*\|\s*([A-Za-z0-9_-]{11})\s*\|/i
                        );

                    const videoId =
                        match
                            ? match[1]
                            : '';

                    const thumbnailUrl =
                        videoId
                            ? 'https://i.ytimg.com/vi/' +
                              videoId +
                              '/hqdefault.jpg'
                            : '';

                    return {

                        pageUrl:
                            getVideoPageUrl(
                                page.title
                            ),

                        videoId:
                            videoId,

                        thumbnailUrl:
                            thumbnailUrl
                    };
                });
        }


        /* --------------------------------------------------
           SHOW RANDOM VIDEO
           -------------------------------------------------- */

        function showRandomVideo() {

            const page =
                chooseRandomVideoPage();

            if (!page) {
                return;
            }

            currentVideo =
                page.title;

            const media =
                videoCard.querySelector(
                    '.vh-feature-media'
                );

            const copy =
                videoCard.querySelector(
                    '.vh-feature-copy'
                );

            if (!media || !copy) {
                return;
            }


            /* ---------- title ---------- */

            copy.innerHTML = '';

            const type =
                document.createElement('small');

            type.textContent =
                'VIDEO';

            const title =
                document.createElement('strong');

            title.textContent =
                getVideoDisplayTitle(
                    page.title
                );

            copy.appendChild(type);
            copy.appendChild(title);


/* ---------- thumbnail + click ---------- */

loadVideoInfo(page)

    .then(function(info) {

        if (
            !info ||
            currentVideo !== page.title
        ) {
            return;
        }


        if (info.thumbnailUrl) {

            const preload =
                new Image();

            preload.onload =
                function() {

                    if (
                        currentVideo ===
                        page.title
                    ) {

                        media.style.backgroundImage =
                            'url("' +
                            info.thumbnailUrl +
                            '")';
                    }
                };

            preload.src =
                info.thumbnailUrl;
        }


        videoCard.onclick =
            function() {

                const target =
                    info.videoId
                        ? 'https://www.youtube.com/watch?v=' +
                          info.videoId
                        : info.pageUrl;

                window.open(
                    target,
                    '_blank',
                    'noopener'
                );
            };

    })

    .catch(function(error) {

        console.error(
            'Homepage Video failed:',
            error
        );
    });
}
        /* --------------------------------------------------
           START
           -------------------------------------------------- */

        loadVideoPages()

            .then(function() {

                console.log(
                    'Homepage Video pages loaded:',
                    videoPages.length
                );

                if (videoPages.length) {

                    showRandomVideo();

                    window.setInterval(
                        showRandomVideo,
                        11500
                    );
                }
            })

            .catch(function(error) {

                console.error(
                    'Homepage Video category failed:',
                    error
                );
            });
    }
}

/* ==========================================================
   HOMEPAGE — RANDOM IMAGE QUOTE
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const imageQuoteCard =
        document.getElementById('vh-random-imagequote');

    if (imageQuoteCard) {

        const IMAGE_API =
            'https://vanimedia.org/w/api.php';

        const MONTH_CATEGORIES = [
            'Category:January Quotes with Images',
            'Category:February Quotes with Images',
            'Category:March Quotes with Images',
            'Category:April Quotes with Images',
            'Category:May Quotes with Images',
            'Category:June Quotes with Images',
            'Category:July Quotes with Images',
            'Category:August Quotes with Images',
            'Category:September Quotes with Images',
            'Category:October Quotes with Images',
            'Category:November Quotes with Images',
            'Category:December Quotes with Images'
        ];

        let imageQuoteFiles = [];
        let currentImageQuote = '';


        /* ======================================================
           LOAD ONE MONTH CATEGORY
           ====================================================== */

        function loadImageQuoteCategory(
            category,
            cmcontinue
        ) {

            let url =
                IMAGE_API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(category) +
                '&cmnamespace=6' +
                '&cmtype=file' +
                '&cmlimit=max';

            if (cmcontinue) {

                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(cmcontinue);
            }

            return fetch(url)

                .then(function(response) {

                    if (!response.ok) {

                        throw new Error(
                            'Image Quote category HTTP ' +
                            response.status
                        );
                    }

                    return response.json();
                })

                .then(function(data) {

                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {

                        data.query.categorymembers
                            .forEach(function(file) {

                                if (file.title) {

                                    imageQuoteFiles.push(
                                        file
                                    );
                                }
                            });
                    }


                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {

                        return loadImageQuoteCategory(
                            category,
                            data.continue.cmcontinue
                        );
                    }
                });
        }


        /* ======================================================
           LOAD ALL MONTHS
           ====================================================== */

        function loadAllImageQuotes() {

            const jobs =
                MONTH_CATEGORIES.map(
                    function(category) {

                        return loadImageQuoteCategory(
                            category
                        );
                    }
                );

            return Promise.all(jobs);
        }


        /* ======================================================
           RANDOM FILE
           ====================================================== */

        function chooseRandomImageQuote() {

            if (!imageQuoteFiles.length) {
                return null;
            }

            let file;

            do {

                file =
                    imageQuoteFiles[
                        Math.floor(
                            Math.random() *
                            imageQuoteFiles.length
                        )
                    ];

            } while (
                imageQuoteFiles.length > 1 &&
                file.title === currentImageQuote
            );

            return file;
        }


        /* ======================================================
           GET THUMBNAIL + FULL IMAGE
           ====================================================== */

        function getImageQuoteInfo(file) {

            const url =
                IMAGE_API +
                '?action=query' +
                '&format=json' +
                '&prop=imageinfo' +
                '&iiprop=url' +
                '&iiurlwidth=500' +
                '&titles=' +
                encodeURIComponent(
                    file.title
                );

            return fetch(url)

                .then(function(response) {

                    if (!response.ok) {

                        throw new Error(
                            'Image Quote image HTTP ' +
                            response.status
                        );
                    }

                    return response.json();
                })

                .then(function(data) {

                    if (
                        !data.query ||
                        !data.query.pages
                    ) {
                        return null;
                    }

                    const page =
                        Object.values(
                            data.query.pages
                        )[0];

                    if (
                        !page ||
                        !page.imageinfo ||
                        !page.imageinfo.length
                    ) {
                        return null;
                    }

                    const info =
                        page.imageinfo[0];

                    const thumbnailUrl =
                        info.thumburl ||
                        info.url ||
                        '';

                    const fullUrl =
                        info.url ||
                        thumbnailUrl;

                    const pageUrl =
                        'https://vanimedia.org/wiki/' +
                        encodeURIComponent(
                            file.title.replace(
                                / /g,
                                '_'
                            )
                        )
                            .replace(/%2F/g, '/')
                            .replace(/%3A/g, ':');


                    return {

                        thumbnailUrl:
                            thumbnailUrl,

                        fullUrl:
                            fullUrl,

                        pageUrl:
                            pageUrl
                    };
                });
        }


        /* ======================================================
           FILENAME → HUMAN DATE
           072866 → July 28, 1966
           ====================================================== */

        function getImageQuoteTitle(
            fileTitle
        ) {

            const clean =
                fileTitle
                    .replace(
                        /^File:/i,
                        ''
                    )
                    .replace(
                        /\.(jpg|jpeg|png|webp)$/i,
                        ''
                    )
                    .replace(
                        /\s*Image-quote$/i,
                        ''
                    )
                    .trim();


            const match =
                clean.match(
                    /^(\d{2})(\d{2})(\d{2})/
                );

            if (!match) {
                return clean;
            }


            const month =
                parseInt(
                    match[1],
                    10
                );

            const day =
                parseInt(
                    match[2],
                    10
                );

            const year =
                1900 +
                parseInt(
                    match[3],
                    10
                );


            const months = [
                'January',
                'February',
                'March',
                'April',
                'May',
                'June',
                'July',
                'August',
                'September',
                'October',
                'November',
                'December'
            ];


            if (
                month < 1 ||
                month > 12 ||
                day < 1 ||
                day > 31
            ) {
                return clean;
            }


            return (
                months[month - 1] +
                ' ' +
                day +
                ', ' +
                year
            );
        }


        /* ======================================================
           LIGHTBOX
           ====================================================== */

        function openImageQuoteLightbox(
            imageUrl
        ) {

            let lightbox =
                document.getElementById(
                    'vh-imagequote-lightbox'
                );


            if (!lightbox) {

                lightbox =
                    document.createElement(
                        'div'
                    );

                lightbox.id =
                    'vh-imagequote-lightbox';


                lightbox.innerHTML =
                    '<button ' +
                    'class="vh-imagequote-lightbox-close" ' +
                    'type="button" ' +
                    'aria-label="Close">×</button>' +
                    '<img ' +
                    'alt="Śrīla Prabhupāda Image Quote">';


                document.body.appendChild(
                    lightbox
                );

lightbox.addEventListener(
    'mouseleave',
    function() {

        lightbox.classList.remove(
            'is-open'
        );
    }
);


                lightbox.addEventListener(
                    'click',
                    function(event) {

                        if (
                            event.target ===
                                lightbox ||
                            event.target
                                .classList
                                .contains(
                                    'vh-imagequote-lightbox-close'
                                )
                        ) {

                            lightbox.classList
                                .remove(
                                    'is-open'
                                );
                        }
                    }
                );


                document.addEventListener(
                    'keydown',
                    function(event) {

                        if (
                            event.key ===
                            'Escape'
                        ) {

                            lightbox.classList
                                .remove(
                                    'is-open'
                                );
                        }
                    }
                );
            }


            const image =
                lightbox.querySelector(
                    'img'
                );

            image.src =
                imageUrl;
const cardRect =
    imageQuoteCard.getBoundingClientRect();

lightbox.classList.add(
    'is-open'
);

requestAnimationFrame(function() {

    const popupRect =
        lightbox.getBoundingClientRect();

    /*
     * Place popup immediately to the
     * right of the Image Quote card.
     */

    let left =
        cardRect.right - 240;

    let top =
        cardRect.top - 220;


    /*
     * If there is not enough room
     * on the right, place it above.
     */

    if (
        left +
        popupRect.width >
        window.innerWidth - 10
    ) {

        left =
            cardRect.right -
            popupRect.width;

        top =
            cardRect.top -
            popupRect.height -
            12;
    }


    /*
     * Keep inside viewport.
     */

    left =
        Math.max(
            10,
            Math.min(
                left,
                window.innerWidth -
                popupRect.width -
                10
            )
        );

    top =
        Math.max(
            10,
            top
        );


    lightbox.style.left =
        left + 'px';

    lightbox.style.top =
        top + 'px';
});
        }


        /* ======================================================
           DISPLAY RANDOM IMAGE QUOTE
           ====================================================== */

        function showRandomImageQuote() {

            const file =
                chooseRandomImageQuote();

            if (!file) {
                return;
            }


            currentImageQuote =
                file.title;


            const media =
                imageQuoteCard.querySelector(
                    '.vh-feature-media'
                );

            const copy =
                imageQuoteCard.querySelector(
                    '.vh-feature-copy'
                );


            if (!media || !copy) {
                return;
            }


            /*
             * Card text
             */

            copy.innerHTML = '';


            const type =
                document.createElement(
                    'small'
                );

            type.textContent =
                'IMAGE QUOTE';


            const title =
                document.createElement(
                    'strong'
                );

            title.textContent =
                getImageQuoteTitle(
                    file.title
                );


            copy.appendChild(
                type
            );

            copy.appendChild(
                title
            );


            /*
             * Load image
             */

            getImageQuoteInfo(
                file
            )

                .then(function(info) {

                    if (
                        !info ||
                        currentImageQuote !==
                            file.title
                    ) {
                        return;
                    }


                    if (
                        info.thumbnailUrl
                    ) {

                        const preload =
                            new Image();


                        preload.onload =
                            function() {

                                if (
                                    currentImageQuote ===
                                    file.title
                                ) {

                                    media.style
                                        .backgroundImage =
                                        'url("' +
                                        info.thumbnailUrl +
                                        '")';
                                }
                            };


                        preload.src =
                            info.thumbnailUrl;
                    }


                    /*
                     * Open full image
                     * in lightbox
                     */

                    imageQuoteCard.onclick =
                        function() {

                            if (
                                info.fullUrl
                            ) {

                                openImageQuoteLightbox(
                                    info.fullUrl
                                );
                            }
                        };
                })

                .catch(function(error) {

                    console.error(
                        'Homepage Image Quote failed:',
                        error
                    );
                });
        }


        /* ======================================================
           START
           ====================================================== */

        loadAllImageQuotes()

            .then(function() {

                console.log(
                    'Homepage Image Quotes loaded:',
                    imageQuoteFiles.length
                );


                if (
                    imageQuoteFiles.length
                ) {

                    showRandomImageQuote();


                    window.setInterval(
                        showRandomImageQuote,
                        9800
                    );
                }
            })

            .catch(function(error) {

                console.error(
                    'Homepage Image Quote categories failed:',
                    error
                );
            });
    }
}

/* ==========================================================
   HOMEPAGE — RANDOM NECTAR DROP
   ========================================================== */

if (mw.config.get('wgPageName') === 'Main_Page') {

    const nectarCard =
        document.getElementById('vh-random-nectar');

    if (nectarCard) {

        const NECTAR_API =
            'https://vanipedia.org/w/api.php';

        const NECTAR_CATEGORY =
            'Category:Nectar Drops from Srila Prabhupada';

        let nectarPages = [];
        let currentNectar = '';

        let nectarAudio = null;
        let nectarLocked = false;


        const audioArea =
            nectarCard.querySelector(
                '.vh-feature-audio'
            );

        const audioIcon =
            nectarCard.querySelector(
                '.vh-feature-audio-icon'
            );

const nectarWave =
    document.createElement(
        'div'
    );

nectarWave.className =
    'vh-nectar-wave';

nectarWave.innerHTML =
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>' +
    '<span></span>';

audioArea.appendChild(
    nectarWave
);

        const copy =
            nectarCard.querySelector(
                '.vh-feature-copy'
            );


        /* ======================================================
           LOAD ALL NECTAR DROP PAGES
           ====================================================== */

        function loadNectarPages(
            cmcontinue
        ) {

            let url =
                NECTAR_API +
                '?action=query' +
                '&format=json' +
                '&list=categorymembers' +
                '&cmtitle=' +
                encodeURIComponent(
                    NECTAR_CATEGORY
                ) +
                '&cmnamespace=0' +
                '&cmtype=page' +
                '&cmlimit=max';


            if (cmcontinue) {

                url +=
                    '&cmcontinue=' +
                    encodeURIComponent(
                        cmcontinue
                    );
            }


            return fetch(url)

                .then(function(response) {

                    if (!response.ok) {

                        throw new Error(
                            'Nectar category HTTP ' +
                            response.status
                        );
                    }

                    return response.json();
                })

                .then(function(data) {

                    if (
                        data.query &&
                        data.query.categorymembers
                    ) {

                        data.query.categorymembers
                            .forEach(function(page) {

                                if (page.title) {

                                    nectarPages.push(
                                        page
                                    );
                                }
                            });
                    }


                    if (
                        data.continue &&
                        data.continue.cmcontinue
                    ) {

                        return loadNectarPages(
                            data.continue.cmcontinue
                        );
                    }
                });
        }


        /* ======================================================
           CHOOSE RANDOM NECTAR DROP
           ====================================================== */

        function chooseRandomNectar() {

            if (!nectarPages.length) {
                return null;
            }


            let page;

            do {

                page =
                    nectarPages[
                        Math.floor(
                            Math.random() *
                            nectarPages.length
                        )
                    ];

            } while (
                nectarPages.length > 1 &&
                page.title === currentNectar
            );


            return page;
        }


        /* ======================================================
           CREATE SHORT MEANINGFUL TITLE FROM QUOTE
           ====================================================== */

        function getNectarShortText(
            quote
        ) {

            if (!quote) {
                return 'Śrīla Prabhupāda speaks';
            }


            let text =
                quote

                    .replace(
                        /^["“”]+|["“”]+$/g,
                        ''
                    )

                    .replace(
                        /\s+/g,
                        ' '
                    )

                    .trim();


            /*
             * Prefer first sentence when it is
             * reasonably short.
             */

            const sentenceMatch =
                text.match(
                    /^(.+?[.!?])(?:\s|$)/
                );


            if (
                sentenceMatch &&
                sentenceMatch[1].length <= 85
            ) {

                return sentenceMatch[1];
            }


            /*
             * Otherwise take approximately
             * the first 70 characters,
             * ending at a full word.
             */

            if (text.length > 72) {

                text =
                    text.substring(
                        0,
                        72
                    );

                const lastSpace =
                    text.lastIndexOf(
                        ' '
                    );

                if (lastSpace > 45) {

                    text =
                        text.substring(
                            0,
                            lastSpace
                        );
                }

                text += '…';
            }


            return text;
        }


        /* ======================================================
           GET QUOTE TEXT + AUDIO URL
           ====================================================== */

        function getNectarInfo(
            page
        ) {

            const url =
                NECTAR_API +
                '?action=parse' +
                '&format=json' +
                '&page=' +
                encodeURIComponent(
                    page.title
                ) +
                '&prop=text';


            return fetch(url)

                .then(function(response) {

                    if (!response.ok) {

                        throw new Error(
                            'Nectar page HTTP ' +
                            response.status
                        );
                    }

                    return response.json();
                })

                .then(function(data) {

                    if (
                        !data.parse ||
                        !data.parse.text ||
                        !data.parse.text['*']
                    ) {
                        return null;
                    }


                    const wrapper =
                        document.createElement(
                            'div'
                        );

                    wrapper.innerHTML =
                        data.parse.text['*'];


                    /*
                     * Quote
                     */

                    const quoteElement =
                        wrapper.querySelector(
                            '.nectardroptext'
                        );

                    const quote =
                        quoteElement
                            ? quoteElement
                                .textContent
                                .replace(
                                    /\s+/g,
                                    ' '
                                )
                                .trim()
                            : '';


                    /*
                     * MP3 source
                     */

                    const source =
                        wrapper.querySelector(
                            'audio source'
                        );

                    const audioUrl =
                        source
                            ? source.getAttribute(
                                'src'
                            )
                            : '';


                    return {

                        quote:
                            quote,

                        audioUrl:
                            audioUrl
                    };
                });
        }


        /* ======================================================
           RESET PLAY BUTTON
           ====================================================== */

        function resetNectarPlayButton() {

            if (audioIcon) {

                audioIcon.textContent =
                    '▶';
            }
        }


        /* ======================================================
           PREPARE AUDIO
           ====================================================== */

        function prepareNectarAudio(
            audioUrl
        ) {

            /*
             * Dispose of previous audio object.
             */

            if (nectarAudio) {

                nectarAudio.pause();

                nectarAudio.src = '';

                nectarAudio = null;
            }


            resetNectarPlayButton();


            if (!audioUrl) {
                return;
            }


            nectarAudio =
                new Audio(
                    audioUrl
                );

            nectarAudio.preload =
                'metadata';


            /*
             * Audio finished naturally.
             */

/*
 * Audio finished naturally.
 */

nectarAudio.addEventListener(
    'ended',
    function() {

        resetNectarPlayButton();

        nectarWave.classList.remove(
            'is-playing'
        );

        nectarLocked =
            false;
    }
);


/*
 * Audio starts / resumes.
 */

nectarAudio.addEventListener(
    'play',
    function() {

        if (audioIcon) {

            audioIcon.textContent =
                '❚❚';
        }

        nectarWave.classList.add(
            'is-playing'
        );
    }
);


/*
 * Audio pauses.
 */

nectarAudio.addEventListener(
    'pause',
    function() {

        if (
            nectarAudio &&
            !nectarAudio.ended &&
            audioIcon
        ) {

            audioIcon.textContent =
                '▶';
        }

        nectarWave.classList.remove(
            'is-playing'
        );
    }
);
}
        /* ======================================================
           PLAY / PAUSE
           ====================================================== */

        function toggleNectarAudio(
            event
        ) {

            if (event) {

                event.preventDefault();
                event.stopPropagation();
            }


            if (!nectarAudio) {
                return;
            }


            /*
             * Playing → pause
             */

if (!nectarAudio.paused) {

    nectarAudio.pause();

    nectarLocked =
        false;

    return;
}

            /*
             * Paused → play/resume
             */

            nectarLocked =
                true;


            nectarAudio
                .play()

                .catch(function(error) {

                    console.error(
                        'Nectar Drop audio failed:',
                        error
                    );
                });
        }


        /* ======================================================
           DISPLAY RANDOM NECTAR DROP
           ====================================================== */

        function showRandomNectar() {

            /*
             * Never change the card while
             * user is listening or has paused it.
             */

            if (nectarLocked) {
                return;
            }


            const page =
                chooseRandomNectar();

            if (!page) {
                return;
            }


            currentNectar =
                page.title;


            if (!copy) {
                return;
            }


            copy.innerHTML = '';


            const type =
                document.createElement(
                    'small'
                );

            type.textContent =
                'NECTAR DROP';


            const title =
                document.createElement(
                    'strong'
                );

            title.textContent =
                '';

            copy.appendChild(
                type
            );

            copy.appendChild(
                title
            );


            getNectarInfo(
                page
            )

                .then(function(info) {

                    if (
                        !info ||
                        currentNectar !==
                            page.title
                    ) {
                        return;
                    }


                    /*
                     * Display meaningful words
                     * directly from Nectar Drop.
                     */

                    title.textContent =
                        getNectarShortText(
                            info.quote
                        );


                    /*
                     * Prepare MP3.
                     */

                    prepareNectarAudio(
                        info.audioUrl
                    );
                })

                .catch(function(error) {

                    console.error(
                        'Homepage Nectar Drop failed:',
                        error
                    );

                    title.textContent =
                        'Śrīla Prabhupāda speaks';
                });
        }


        /* ======================================================
           PLAY BUTTON CLICK
           ====================================================== */
        if (audioArea) {

            audioArea.addEventListener(
                'click',
                toggleNectarAudio
            );
        }


        /* ======================================================
           START
           ====================================================== */

        loadNectarPages()

            .then(function() {

                console.log(
                    'Homepage Nectar Drops loaded:',
                    nectarPages.length
                );


                if (
                    nectarPages.length
                ) {

                    showRandomNectar();


                    window.setInterval(
                        function() {

                            if (!nectarLocked) {

                                showRandomNectar();
                            }

                        },
                        12700
                    );
                }
            })

            .catch(function(error) {

                console.error(
                    'Homepage Nectar category failed:',
                    error
                );
            });
    }
}

const networkMission =
    document.getElementById('vh-network-mission');

if (networkMission) {

    networkMission.addEventListener(
        'click',
        function() {
            window.location.href =
                mw.util.getUrl('Vanipedia_Vision');
        }
    );

}

/* CLOSE HOMEPAGE WRAPPER */
})();

/* =========================================================
   VANIPEDIA FLIPBOOK POPUP
   ========================================================= */

(function () {

    function createFlipbookOverlay() {

        if (document.getElementById('vani-flipbook-overlay')) {
            return;
        }

        var overlay = document.createElement('div');
        overlay.id = 'vani-flipbook-overlay';

        var modal = document.createElement('div');
        modal.className = 'vani-flipbook-modal';

        var closeButton = document.createElement('button');
        closeButton.className = 'vani-flipbook-close';
        closeButton.type = 'button';
        closeButton.setAttribute('aria-label', 'Close flipbook');
        closeButton.innerHTML = '&times;';

        var frameHolder = document.createElement('div');
        frameHolder.id = 'vani-flipbook-frame-holder';

        modal.appendChild(closeButton);
        modal.appendChild(frameHolder);
        overlay.appendChild(modal);

        document.body.appendChild(overlay);


        closeButton.addEventListener('click', function () {
            closeFlipbook();
        });


        overlay.addEventListener('click', function (event) {

            if (event.target === overlay) {
                closeFlipbook();
            }

        });


        document.addEventListener('keydown', function (event) {

            if (
                event.key === 'Escape' &&
                overlay.classList.contains('is-open')
            ) {
                closeFlipbook();
            }

        });

    }


    function openFlipbook(book) {

        createFlipbookOverlay();

        var overlay =
            document.getElementById('vani-flipbook-overlay');

        var holder =
            document.getElementById('vani-flipbook-frame-holder');


        /*
         * FIRST make popup visible.
         */

        overlay.classList.add('is-open');

        document.body.classList.add('vani-flipbook-open');


        /*
         * Remove any previous iframe.
         */

        holder.innerHTML = '';


        /*
         * Wait until browser has calculated
         * the real popup dimensions.
         */

        requestAnimationFrame(function () {

            requestAnimationFrame(function () {

                var frame =
                    document.createElement('iframe');

                frame.id =
                    'vani-flipbook-frame';

                frame.title =
                    'Vanipedia Flipbook';

                frame.setAttribute(
                    'allowfullscreen',
                    'true'
                );

frame.src =
    'https://dev.vanipedia.org/flipbook/?book=' +
    encodeURIComponent(book);

                holder.appendChild(frame);

            });

        });

    }


    function closeFlipbook() {

        var overlay =
            document.getElementById('vani-flipbook-overlay');

        var holder =
            document.getElementById('vani-flipbook-frame-holder');


        if (!overlay) {
            return;
        }


        overlay.classList.remove('is-open');

        document.body.classList.remove('vani-flipbook-open');


        if (holder) {
            holder.innerHTML = '';
        }

    }


    function initialiseFlipbooks() {

        var triggers =
            document.querySelectorAll(
                '.vani-flipbook-trigger'
            );


        triggers.forEach(function (trigger) {

            if (
                trigger.getAttribute(
                    'data-flipbook-ready'
                ) === '1'
            ) {
                return;
            }


            trigger.setAttribute(
                'data-flipbook-ready',
                '1'
            );


            trigger.addEventListener(
                'click',
                function (event) {

                    event.preventDefault();


                    var book =
                        trigger.getAttribute(
                            'data-book'
                        );


                    if (book) {
                        openFlipbook(book);
                    }

                }
            );

        });

    }


    createFlipbookOverlay();

    initialiseFlipbooks();


    if (
        window.mw &&
        mw.hook
    ) {

        mw.hook(
            'wikipage.content'
        ).add(function () {

            initialiseFlipbooks();

        });

    }

})();