MediaWiki:Common.js: Difference between revisions

From UO Renaissance Wiki
Jump to navigation Jump to search
Add The World and Skills dropdowns in Compendium sidebar
Position scroll to top button next to main container and above navbar
 
(13 intermediate revisions by the same user not shown)
Line 1: Line 1:
/* UO Renaissance Wiki Common.js */
/* UO Renaissance Wiki Common.js */
(function() {
(function() {
   function initCompendiumDropdowns() {
   var compendiumPortal = document.getElementById('p-Compendium');
    var compendiumPortal = document.getElementById('p-Compendium');
  if (compendiumPortal) {
     if (!compendiumPortal) return;
     function attachSidebarSubmenu(liSelector, titleMatch, items) {
      var li = compendiumPortal.querySelector(liSelector) ||
              compendiumPortal.querySelector('a[href*="' + titleMatch + '"]');
      if (li && li.tagName.toLowerCase() === 'a') {
        li = li.closest('li');
      }
      if (!li || li.querySelector('.uor-sidebar-submenu')) return;
 
      li.classList.add('uor-sidebar-has-submenu');
 
      var link = li.querySelector(':scope > a');
      if (link && !link.querySelector('.uor-submenu-arrow')) {
        var arrow = document.createElement('span');
        arrow.className = 'uor-submenu-arrow';
        arrow.innerHTML = '▸';
        arrow.title = 'View Sections';
        arrow.addEventListener('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          li.classList.toggle('is-open');
        });
        link.appendChild(arrow);
      }


    // 1. The World Dropdown
       var submenu = document.createElement('ul');
    var worldLi = compendiumPortal.querySelector('li#n-The-World') ||
       submenu.className = 'uor-sidebar-submenu';
                  compendiumPortal.querySelector('a[href*="/wiki/index.php/The_World"]')?.closest('li');
       var html = '';
    if (worldLi && !worldLi.querySelector('.uor-world-dropdown-wrap')) {
      for (var i = 0; i < items.length; i++) {
       var worldWrap = document.createElement('div');
         html += '<li><a href="' + items[i].href + '">' + items[i].label + '</a></li>';
       worldWrap.className = 'uor-world-dropdown-wrap';
      }
       worldWrap.innerHTML = '<select class="uor-world-dropdown-select" aria-label="Select World Section">' +
      submenu.innerHTML = html;
        '<option value="/wiki/index.php/The_World">World Sections...</option>' +
       li.appendChild(submenu);
         '<option value="/wiki/index.php/The_World#Towns">Towns</option>' +
    }
        '<option value="/wiki/index.php/The_World#Dungeons">Dungeons</option>' +
        '<option value="/wiki/index.php/The_World#Points_of_Interest">Points of Interest</option>' +
       '</select>';


       var worldSelect = worldWrap.querySelector('select');
    // 1. The World Submenu
       var currentPath = window.location.pathname + window.location.hash;
    attachSidebarSubmenu('li#n-The-World', '/wiki/index.php/The_World', [
       for (var i = 0; i < worldSelect.options.length; i++) {
      { label: 'Towns', href: '/wiki/index.php/The_World#Towns' },
         if (worldSelect.options[i].value && currentPath.indexOf(worldSelect.options[i].value) !== -1) {
      { label: 'Dungeons', href: '/wiki/index.php/The_World#Dungeons' },
          worldSelect.selectedIndex = i;
      { label: 'Points of Interest', href: '/wiki/index.php/The_World#Points_of_Interest' }
          break;
    ]);
        }
 
    // 2. Skills Submenu
    attachSidebarSubmenu('li#n-Skills', '/wiki/index.php/Skills', [
      { label: 'Combat', href: '/wiki/index.php/Skills#Combat' },
       { label: 'Crafting', href: '/wiki/index.php/Skills#Crafting' },
      { label: 'Creatures', href: '/wiki/index.php/Skills#Creatures' },
      { label: 'Lore & Knowledge', href: '/wiki/index.php/Skills#Lore_&_Knowledge' },
      { label: 'Miscellaneous', href: '/wiki/index.php/Skills#Miscellaneous' },
      { label: 'Rogue', href: '/wiki/index.php/Skills#Rogue' },
      { label: 'Spells & Magic', href: '/wiki/index.php/Spells' }
    ]);
 
    // 3. Factions & Guilds Submenu
    attachSidebarSubmenu('li[id*="Factions"], li#n-Factions', '/wiki/index.php/Factions', [
      { label: 'Factions', href: '/wiki/index.php/Factions_%26_Guilds#Factions' },
      { label: 'Order & Chaos', href: '/wiki/index.php/Factions_%26_Guilds#Order_&_Chaos' },
      { label: 'Player run Guilds', href: '/wiki/index.php/Factions_%26_Guilds#Player_run_Guilds' }
    ]);
 
    // 4. Champion Spawns Submenu
    attachSidebarSubmenu('li#n-Champion-Spawns', '/wiki/index.php/Champion_Spawns', [
      { label: 'Overview & Altars', href: '/wiki/index.php/Champion_Spawns#Altars' },
      { label: 'The Six Champions', href: '/wiki/index.php/Champion_Spawns#Champions' },
      { label: 'The Harrower', href: '/wiki/index.php/Champion_Spawns#The_Harrower' },
      { label: 'Rewards & Titles', href: '/wiki/index.php/Champion_Spawns#Rewards' }
    ]);
 
    // 5. Bestiary Submenu
    attachSidebarSubmenu('li#n-Bestiary', '/wiki/index.php/Bestiary', [
       { label: 'Bosses & Champions', href: '/wiki/index.php/Bestiary#Bosses_and_Champions' },
      { label: 'Daemons & Elementals', href: '/wiki/index.php/Bestiary#Daemons_and_Elementals' },
      { label: 'Reptiles & Monstrosities', href: '/wiki/index.php/Bestiary#Reptiles_and_Monstrosities' },
      { label: 'Undead & Tomb Horrors', href: '/wiki/index.php/Bestiary#Undead_and_Horrors' },
      { label: 'Humanoids, Tribes & Beasts', href: '/wiki/index.php/Bestiary#Humanoids_and_Beasts' },
      { label: 'Holiday & Event Specials', href: '/wiki/index.php/Bestiary#Holiday_and_Events' }
    ]);
 
    // 6. Housing & Vendors Submenu
    attachSidebarSubmenu('li[id*="Housing"], li#n-Housing-Vendors', 'Housing', [
      { label: 'Ownership Rules', href: '/wiki/index.php/Housing_System#Home_Ownership_Fundamentals' },
      { label: 'House Catalog', href: '/wiki/index.php/Housing_System#Complete_House_Catalog' },
      { label: 'Security & Tactics', href: '/wiki/index.php/Housing_System#House_Security_&_Fortification' },
      { label: 'Classic Vendors', href: '/wiki/index.php/Housing_System#Classic_Player_Vendors' },
      { label: 'Enhanced Vendors', href: '/wiki/index.php/Housing_System#Enhanced_Vendor_System' },
      { label: 'Quick Reference', href: '/wiki/index.php/Housing_System#Housing_&_Vendor_Quick_Reference' }
    ]);
 
    // 7. Murder & Bounties Submenu
    attachSidebarSubmenu('li[id*="Murder"], li#n-Murder-Bounties', 'Murder', [
      { label: 'Murder Counts', href: '/wiki/index.php/The_Murder_System#Understanding_Murder_Counts' },
      { label: 'Stat Loss Formula', href: '/wiki/index.php/The_Murder_System#Stat_Loss_Formula_&_Penalties' },
      { label: 'Reporting & Bounties', href: '/wiki/index.php/The_Murder_System#Reporting_a_Murder_&_Placing_Bounties' },
      { label: 'The Bounty Board', href: '/wiki/index.php/The_Murder_System#The_Bounty_Board_System' },
      { label: 'Claiming Heads', href: '/wiki/index.php/The_Murder_System#Claiming_Bounties_&_Taking_Heads' },
      { label: 'Quick Reference', href: '/wiki/index.php/The_Murder_System#Murder_&_Bounty_Quick_Reference' }
    ]);
 
    // 8. Patch Notes Archive Submenu
    attachSidebarSubmenu('li#n-Patch-Notes-Archive', '/wiki/index.php/Patch_Notes_Archive', [
       { label: 'Era 1: Alpha & Beta (2012)', href: '/wiki/index.php/Patch_Notes_Archive#Era_1:_Alpha_.26_Beta_Foundations_.28May_2012_-_Sept_2012.29' },
      { label: 'Era 2: Launch & Maturation (2012-2014)', href: '/wiki/index.php/Patch_Notes_Archive#Era_2:_Shard_Launch_.26_Server_Maturation_.28Sept_2012_-_Sept_2014.29' },
      { label: 'Era 3: Golden Age (2014-2016)', href: '/wiki/index.php/Patch_Notes_Archive#Era_3:_The_Golden_Age_.26_Expansions_.28Oct_2014_-_Oct_2016.29' },
      { label: 'Era 4: Veteran & Modern (2016-2020)', href: '/wiki/index.php/Patch_Notes_Archive#Era_4:_Veteran_Shard_.26_Modernization_.28Nov_2016_-_June_2020.29' }
    ]);
 
    // 9. Unique Shard Systems Submenu
    var uniqueSystemsItems = [
      { label: "The Zookeeper's Quest", href: "/wiki/index.php/The_Zookeeper's_Quest" },
      { label: "Ancient MIB's", href: "/wiki/index.php/Ancient_MIB's" }
    ];
 
    function normalizeSystemUrl(url) {
      if (!url) return '';
      try {
        return decodeURIComponent(url).replace(/\/index\.php\//, '/index.php/').replace(/\/+$/, '').toLowerCase();
      } catch(e) {
         return url.toLowerCase();
      }
    }
 
    function isExcludedOrDuplicate(item, list) {
      if (!item || !item.href) return true;
      var nHref = normalizeSystemUrl(item.href);
      var nLbl = (item.label || '').trim().toLowerCase();
      // Exclude the parent page itself
      if (nHref.indexOf('unique_systems') !== -1 || nLbl === 'unique shard systems' || nLbl === 'unique systems') {
        return true;
       }
       }
      return list.some(function(existing) {
        return normalizeSystemUrl(existing.href) === nHref ||
              (existing.label && existing.label.trim().toLowerCase() === nLbl);
      });
    }


       worldSelect.addEventListener('change', function() {
    // Automatically incorporate any additional unique systems stored in localStorage
         if (this.value) {
    try {
           window.location.href = this.value;
       var storedUnique = localStorage.getItem('uor_unique_shard_systems');
      if (storedUnique) {
        var parsedUnique = JSON.parse(storedUnique);
         if (Array.isArray(parsedUnique)) {
           var filteredStorage = [];
          parsedUnique.forEach(function(item) {
            if (!isExcludedOrDuplicate(item, uniqueSystemsItems)) {
              uniqueSystemsItems.push(item);
              filteredStorage.push(item);
            }
          });
          // Update localStorage to strip any invalid/duplicate entries
          localStorage.setItem('uor_unique_shard_systems', JSON.stringify(filteredStorage));
         }
         }
       });
       }
    } catch (e) {}


       worldLi.appendChild(worldWrap);
    // Check if the DOM already has sub-items (e.g. from MediaWiki:Sidebar)
    var uniqueLi = compendiumPortal.querySelector('li[id*="Unique"], li#n-Unique-Shard-Systems') ||
                  compendiumPortal.querySelector('a[href*="/wiki/index.php/Unique_Systems"]');
    if (uniqueLi) {
       if (uniqueLi.tagName.toLowerCase() === 'a') uniqueLi = uniqueLi.closest('li');
      if (uniqueLi) {
        var domSubLinks = uniqueLi.querySelectorAll('ul li a');
        domSubLinks.forEach(function(sl) {
          var lbl = sl.textContent.trim();
          var hrf = sl.getAttribute('href');
          var candidate = { label: lbl, href: hrf };
          if (!isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
            uniqueSystemsItems.push(candidate);
          }
        });
      }
     }
     }


     // 2. Skills Dropdown
     attachSidebarSubmenu('li[id*="Unique"], li#n-Unique-Shard-Systems', '/wiki/index.php/Unique_Systems', uniqueSystemsItems);
    var skillsLi = compendiumPortal.querySelector('li#n-Skills') ||
 
                  compendiumPortal.querySelector('li#n-Skills-Overview') ||
    // Sync any custom unique system links dynamically from the Unique Systems hub page
                  compendiumPortal.querySelector('a[href*="/wiki/index.php/Skills"]')?.closest('li');
     if (window.location.pathname.indexOf('/Unique_Systems') !== -1) {
     if (skillsLi && !skillsLi.querySelector('.uor-skills-dropdown-wrap')) {
       try {
       var wrap = document.createElement('div');
        var pageLinks = document.querySelectorAll('.uor-unique-system-link, [data-shard-system]');
      wrap.className = 'uor-skills-dropdown-wrap';
        if (pageLinks.length > 0) {
      wrap.innerHTML = '<select class="uor-skills-dropdown-select" aria-label="Select Skill Section">' +
          var collected = [];
        '<option value="/wiki/index.php/Skills">Skill Sections...</option>' +
          pageLinks.forEach(function(pl) {
        '<option value="/wiki/index.php/Skills#Combat">Combat</option>' +
            var plLabel = pl.getAttribute('data-shard-system') || pl.textContent.trim();
        '<option value="/wiki/index.php/Skills#Crafting">Crafting</option>' +
            var plHref = pl.getAttribute('href');
        '<option value="/wiki/index.php/Skills#Creatures">Creatures</option>' +
            var candidate = { label: plLabel, href: plHref };
        '<option value="/wiki/index.php/Skills#Lore_&_Knowledge">Lore & Knowledge</option>' +
            if (!isExcludedOrDuplicate(candidate, collected) && !isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
        '<option value="/wiki/index.php/Skills#Miscellaneous">Miscellaneous</option>' +
              collected.push(candidate);
        '<option value="/wiki/index.php/Skills#Rogue">Rogue</option>' +
            }
         '<option value="/wiki/index.php/Spells">Spells & Magic</option>' +
          });
       '</select>';
          if (collected.length > 0) {
            localStorage.setItem('uor_unique_shard_systems', JSON.stringify(collected));
          }
         }
       } catch (e) {}
    }


       var select = wrap.querySelector('select');
    document.addEventListener('click', function(e) {
      var currentPath = window.location.pathname + window.location.hash;
       if (!e.target.closest('.uor-sidebar-has-submenu')) {
      for (var i = 0; i < select.options.length; i++) {
        var openMenus = document.querySelectorAll('.uor-sidebar-has-submenu.is-open');
        if (select.options[i].value && currentPath.indexOf(select.options[i].value) !== -1) {
        for (var i = 0; i < openMenus.length; i++) {
          select.selectedIndex = i;
          openMenus[i].classList.remove('is-open');
          break;
         }
         }
       }
       }
    });
  }
})();
/* Floating Scroll to Top Arrow Button */
(function() {
  function initScrollToTop() {
    var existing = document.getElementById('uor-scroll-top');
    if (existing) existing.remove();
    var btn = document.createElement('a');
    btn.id = 'uor-scroll-top';
    btn.className = 'uor-scroll-top-btn';
    btn.href = '#top';
    btn.title = 'Scroll to Top';
    btn.setAttribute('aria-label', 'Scroll to top of page');
    btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 4l-8 8h5v8h6v-8h5z"/></svg>';
    btn.addEventListener('click', function(e) {
      e.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    });


       select.addEventListener('change', function() {
    document.body.appendChild(btn);
         if (this.value) {
 
           window.location.href = this.value;
    function repositionButton() {
       var mainBox = document.getElementById('content') || document.querySelector('.mw-body');
      if (mainBox) {
        var rect = mainBox.getBoundingClientRect();
        var spaceRight = window.innerWidth - rect.right;
        // If there's at least 54px room to the right of the container, stick right beside it
         if (spaceRight >= 54) {
          btn.style.left = Math.round(rect.right + 10) + 'px';
          btn.style.right = 'auto';
        } else {
           // Narrow viewport / mobile: dock to right screen edge
          btn.style.left = 'auto';
          btn.style.right = '16px';
         }
         }
       });
       } else {
        btn.style.left = 'auto';
        btn.style.right = '16px';
      }
    }


       skillsLi.appendChild(wrap);
    var ticking = false;
    function updateState() {
      var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
       if (scrollY > 220) {
        btn.classList.add('is-visible');
      } else {
        btn.classList.remove('is-visible');
      }
      repositionButton();
      ticking = false;
     }
     }
    window.addEventListener('scroll', function() {
      if (!ticking) {
        window.requestAnimationFrame(updateState);
        ticking = true;
      }
    }, { passive: true });
    window.addEventListener('resize', function() {
      repositionButton();
    }, { passive: true });
    // Initial check
    updateState();
    setTimeout(repositionButton, 200);
    setTimeout(repositionButton, 1000);
   }
   }


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

Latest revision as of 18:28, 9 September 2026

/* UO Renaissance Wiki Common.js */
(function() {
  var compendiumPortal = document.getElementById('p-Compendium');
  if (compendiumPortal) {
    function attachSidebarSubmenu(liSelector, titleMatch, items) {
      var li = compendiumPortal.querySelector(liSelector) ||
               compendiumPortal.querySelector('a[href*="' + titleMatch + '"]');
      if (li && li.tagName.toLowerCase() === 'a') {
        li = li.closest('li');
      }
      if (!li || li.querySelector('.uor-sidebar-submenu')) return;

      li.classList.add('uor-sidebar-has-submenu');

      var link = li.querySelector(':scope > a');
      if (link && !link.querySelector('.uor-submenu-arrow')) {
        var arrow = document.createElement('span');
        arrow.className = 'uor-submenu-arrow';
        arrow.innerHTML = '&#9656;';
        arrow.title = 'View Sections';
        arrow.addEventListener('click', function(e) {
          e.preventDefault();
          e.stopPropagation();
          li.classList.toggle('is-open');
        });
        link.appendChild(arrow);
      }

      var submenu = document.createElement('ul');
      submenu.className = 'uor-sidebar-submenu';
      var html = '';
      for (var i = 0; i < items.length; i++) {
        html += '<li><a href="' + items[i].href + '">' + items[i].label + '</a></li>';
      }
      submenu.innerHTML = html;
      li.appendChild(submenu);
    }

    // 1. The World Submenu
    attachSidebarSubmenu('li#n-The-World', '/wiki/index.php/The_World', [
      { label: 'Towns', href: '/wiki/index.php/The_World#Towns' },
      { label: 'Dungeons', href: '/wiki/index.php/The_World#Dungeons' },
      { label: 'Points of Interest', href: '/wiki/index.php/The_World#Points_of_Interest' }
    ]);

    // 2. Skills Submenu
    attachSidebarSubmenu('li#n-Skills', '/wiki/index.php/Skills', [
      { label: 'Combat', href: '/wiki/index.php/Skills#Combat' },
      { label: 'Crafting', href: '/wiki/index.php/Skills#Crafting' },
      { label: 'Creatures', href: '/wiki/index.php/Skills#Creatures' },
      { label: 'Lore & Knowledge', href: '/wiki/index.php/Skills#Lore_&_Knowledge' },
      { label: 'Miscellaneous', href: '/wiki/index.php/Skills#Miscellaneous' },
      { label: 'Rogue', href: '/wiki/index.php/Skills#Rogue' },
      { label: 'Spells & Magic', href: '/wiki/index.php/Spells' }
    ]);

    // 3. Factions & Guilds Submenu
    attachSidebarSubmenu('li[id*="Factions"], li#n-Factions', '/wiki/index.php/Factions', [
      { label: 'Factions', href: '/wiki/index.php/Factions_%26_Guilds#Factions' },
      { label: 'Order & Chaos', href: '/wiki/index.php/Factions_%26_Guilds#Order_&_Chaos' },
      { label: 'Player run Guilds', href: '/wiki/index.php/Factions_%26_Guilds#Player_run_Guilds' }
    ]);

    // 4. Champion Spawns Submenu
    attachSidebarSubmenu('li#n-Champion-Spawns', '/wiki/index.php/Champion_Spawns', [
      { label: 'Overview & Altars', href: '/wiki/index.php/Champion_Spawns#Altars' },
      { label: 'The Six Champions', href: '/wiki/index.php/Champion_Spawns#Champions' },
      { label: 'The Harrower', href: '/wiki/index.php/Champion_Spawns#The_Harrower' },
      { label: 'Rewards & Titles', href: '/wiki/index.php/Champion_Spawns#Rewards' }
    ]);

    // 5. Bestiary Submenu
    attachSidebarSubmenu('li#n-Bestiary', '/wiki/index.php/Bestiary', [
      { label: 'Bosses & Champions', href: '/wiki/index.php/Bestiary#Bosses_and_Champions' },
      { label: 'Daemons & Elementals', href: '/wiki/index.php/Bestiary#Daemons_and_Elementals' },
      { label: 'Reptiles & Monstrosities', href: '/wiki/index.php/Bestiary#Reptiles_and_Monstrosities' },
      { label: 'Undead & Tomb Horrors', href: '/wiki/index.php/Bestiary#Undead_and_Horrors' },
      { label: 'Humanoids, Tribes & Beasts', href: '/wiki/index.php/Bestiary#Humanoids_and_Beasts' },
      { label: 'Holiday & Event Specials', href: '/wiki/index.php/Bestiary#Holiday_and_Events' }
    ]);

    // 6. Housing & Vendors Submenu
    attachSidebarSubmenu('li[id*="Housing"], li#n-Housing-Vendors', 'Housing', [
      { label: 'Ownership Rules', href: '/wiki/index.php/Housing_System#Home_Ownership_Fundamentals' },
      { label: 'House Catalog', href: '/wiki/index.php/Housing_System#Complete_House_Catalog' },
      { label: 'Security & Tactics', href: '/wiki/index.php/Housing_System#House_Security_&_Fortification' },
      { label: 'Classic Vendors', href: '/wiki/index.php/Housing_System#Classic_Player_Vendors' },
      { label: 'Enhanced Vendors', href: '/wiki/index.php/Housing_System#Enhanced_Vendor_System' },
      { label: 'Quick Reference', href: '/wiki/index.php/Housing_System#Housing_&_Vendor_Quick_Reference' }
    ]);

    // 7. Murder & Bounties Submenu
    attachSidebarSubmenu('li[id*="Murder"], li#n-Murder-Bounties', 'Murder', [
      { label: 'Murder Counts', href: '/wiki/index.php/The_Murder_System#Understanding_Murder_Counts' },
      { label: 'Stat Loss Formula', href: '/wiki/index.php/The_Murder_System#Stat_Loss_Formula_&_Penalties' },
      { label: 'Reporting & Bounties', href: '/wiki/index.php/The_Murder_System#Reporting_a_Murder_&_Placing_Bounties' },
      { label: 'The Bounty Board', href: '/wiki/index.php/The_Murder_System#The_Bounty_Board_System' },
      { label: 'Claiming Heads', href: '/wiki/index.php/The_Murder_System#Claiming_Bounties_&_Taking_Heads' },
      { label: 'Quick Reference', href: '/wiki/index.php/The_Murder_System#Murder_&_Bounty_Quick_Reference' }
    ]);

    // 8. Patch Notes Archive Submenu
    attachSidebarSubmenu('li#n-Patch-Notes-Archive', '/wiki/index.php/Patch_Notes_Archive', [
      { label: 'Era 1: Alpha & Beta (2012)', href: '/wiki/index.php/Patch_Notes_Archive#Era_1:_Alpha_.26_Beta_Foundations_.28May_2012_-_Sept_2012.29' },
      { label: 'Era 2: Launch & Maturation (2012-2014)', href: '/wiki/index.php/Patch_Notes_Archive#Era_2:_Shard_Launch_.26_Server_Maturation_.28Sept_2012_-_Sept_2014.29' },
      { label: 'Era 3: Golden Age (2014-2016)', href: '/wiki/index.php/Patch_Notes_Archive#Era_3:_The_Golden_Age_.26_Expansions_.28Oct_2014_-_Oct_2016.29' },
      { label: 'Era 4: Veteran & Modern (2016-2020)', href: '/wiki/index.php/Patch_Notes_Archive#Era_4:_Veteran_Shard_.26_Modernization_.28Nov_2016_-_June_2020.29' }
    ]);

    // 9. Unique Shard Systems Submenu
    var uniqueSystemsItems = [
      { label: "The Zookeeper's Quest", href: "/wiki/index.php/The_Zookeeper's_Quest" },
      { label: "Ancient MIB's", href: "/wiki/index.php/Ancient_MIB's" }
    ];

    function normalizeSystemUrl(url) {
      if (!url) return '';
      try {
        return decodeURIComponent(url).replace(/\/index\.php\//, '/index.php/').replace(/\/+$/, '').toLowerCase();
      } catch(e) {
        return url.toLowerCase();
      }
    }

    function isExcludedOrDuplicate(item, list) {
      if (!item || !item.href) return true;
      var nHref = normalizeSystemUrl(item.href);
      var nLbl = (item.label || '').trim().toLowerCase();
      // Exclude the parent page itself
      if (nHref.indexOf('unique_systems') !== -1 || nLbl === 'unique shard systems' || nLbl === 'unique systems') {
        return true;
      }
      return list.some(function(existing) {
        return normalizeSystemUrl(existing.href) === nHref ||
               (existing.label && existing.label.trim().toLowerCase() === nLbl);
      });
    }

    // Automatically incorporate any additional unique systems stored in localStorage
    try {
      var storedUnique = localStorage.getItem('uor_unique_shard_systems');
      if (storedUnique) {
        var parsedUnique = JSON.parse(storedUnique);
        if (Array.isArray(parsedUnique)) {
          var filteredStorage = [];
          parsedUnique.forEach(function(item) {
            if (!isExcludedOrDuplicate(item, uniqueSystemsItems)) {
              uniqueSystemsItems.push(item);
              filteredStorage.push(item);
            }
          });
          // Update localStorage to strip any invalid/duplicate entries
          localStorage.setItem('uor_unique_shard_systems', JSON.stringify(filteredStorage));
        }
      }
    } catch (e) {}

    // Check if the DOM already has sub-items (e.g. from MediaWiki:Sidebar)
    var uniqueLi = compendiumPortal.querySelector('li[id*="Unique"], li#n-Unique-Shard-Systems') ||
                   compendiumPortal.querySelector('a[href*="/wiki/index.php/Unique_Systems"]');
    if (uniqueLi) {
      if (uniqueLi.tagName.toLowerCase() === 'a') uniqueLi = uniqueLi.closest('li');
      if (uniqueLi) {
        var domSubLinks = uniqueLi.querySelectorAll('ul li a');
        domSubLinks.forEach(function(sl) {
          var lbl = sl.textContent.trim();
          var hrf = sl.getAttribute('href');
          var candidate = { label: lbl, href: hrf };
          if (!isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
            uniqueSystemsItems.push(candidate);
          }
        });
      }
    }

    attachSidebarSubmenu('li[id*="Unique"], li#n-Unique-Shard-Systems', '/wiki/index.php/Unique_Systems', uniqueSystemsItems);

    // Sync any custom unique system links dynamically from the Unique Systems hub page
    if (window.location.pathname.indexOf('/Unique_Systems') !== -1) {
      try {
        var pageLinks = document.querySelectorAll('.uor-unique-system-link, [data-shard-system]');
        if (pageLinks.length > 0) {
          var collected = [];
          pageLinks.forEach(function(pl) {
            var plLabel = pl.getAttribute('data-shard-system') || pl.textContent.trim();
            var plHref = pl.getAttribute('href');
            var candidate = { label: plLabel, href: plHref };
            if (!isExcludedOrDuplicate(candidate, collected) && !isExcludedOrDuplicate(candidate, uniqueSystemsItems)) {
              collected.push(candidate);
            }
          });
          if (collected.length > 0) {
            localStorage.setItem('uor_unique_shard_systems', JSON.stringify(collected));
          }
        }
      } catch (e) {}
    }

    document.addEventListener('click', function(e) {
      if (!e.target.closest('.uor-sidebar-has-submenu')) {
        var openMenus = document.querySelectorAll('.uor-sidebar-has-submenu.is-open');
        for (var i = 0; i < openMenus.length; i++) {
          openMenus[i].classList.remove('is-open');
        }
      }
    });
  }
})();

/* Floating Scroll to Top Arrow Button */
(function() {
  function initScrollToTop() {
    var existing = document.getElementById('uor-scroll-top');
    if (existing) existing.remove();

    var btn = document.createElement('a');
    btn.id = 'uor-scroll-top';
    btn.className = 'uor-scroll-top-btn';
    btn.href = '#top';
    btn.title = 'Scroll to Top';
    btn.setAttribute('aria-label', 'Scroll to top of page');
    btn.innerHTML = '<svg viewBox="0 0 24 24"><path d="M12 4l-8 8h5v8h6v-8h5z"/></svg>';

    btn.addEventListener('click', function(e) {
      e.preventDefault();
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    });

    document.body.appendChild(btn);

    function repositionButton() {
      var mainBox = document.getElementById('content') || document.querySelector('.mw-body');
      if (mainBox) {
        var rect = mainBox.getBoundingClientRect();
        var spaceRight = window.innerWidth - rect.right;
        // If there's at least 54px room to the right of the container, stick right beside it
        if (spaceRight >= 54) {
          btn.style.left = Math.round(rect.right + 10) + 'px';
          btn.style.right = 'auto';
        } else {
          // Narrow viewport / mobile: dock to right screen edge
          btn.style.left = 'auto';
          btn.style.right = '16px';
        }
      } else {
        btn.style.left = 'auto';
        btn.style.right = '16px';
      }
    }

    var ticking = false;
    function updateState() {
      var scrollY = window.pageYOffset || document.documentElement.scrollTop || 0;
      if (scrollY > 220) {
        btn.classList.add('is-visible');
      } else {
        btn.classList.remove('is-visible');
      }
      repositionButton();
      ticking = false;
    }

    window.addEventListener('scroll', function() {
      if (!ticking) {
        window.requestAnimationFrame(updateState);
        ticking = true;
      }
    }, { passive: true });

    window.addEventListener('resize', function() {
      repositionButton();
    }, { passive: true });

    // Initial check
    updateState();
    setTimeout(repositionButton, 200);
    setTimeout(repositionButton, 1000);
  }

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