MediaWiki:Common.js: Difference between revisions

From CoraTO Wiki - Official Wiki
Jump to navigation Jump to search
No edit summary
Tags: Manual revert Reverted
No edit summary
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
/**
/*
* MediaWiki Common JavaScript Module - Enhanced Version
Propósito da funcionalidade:
* Unified version with Quest Tracker functionality integrated
- Criar e controlar um botão “voltar ao topo” que aparece após rolar a página.
* Designed for MediaWiki site usage with Vector Legacy skin support
- Exibir/ocultar com classes (`show` e `pulse`) e realizar rolagem suave até o topo ao clicar.
*
- Inserir automaticamente o botão no `document.body` e registrar listeners de `scroll` (passivo) e `click`.
* @author NewCora Wiki Team
*/
* @version 3.0
* @license MIT
*/


(function() {
(function () {
   'use strict';
   'use strict';


  // ============================================================================
  // CONFIGURATION & CONSTANTS
  // ============================================================================
  /**
  * Configuration object for the module
  * @type {Object}
  */
   const CONFIG = {
   const CONFIG = {
    SELECTORS: {
      TAB_CONTENT: '.tab-content',
      NAV_TAB: '.nav-tab, .nav-tab-fgod',
      CARD_LINKS: '.card[data-link], .destaque-card[data-link]',
      COLLAPSIBLE_HEADER: '.collapsible-header',
      COLLAPSIBLE_BUTTON: '.collapsible',
      GUARDIAN_ACCORDION: '.guardian-accordion',
      // Quest Tracker selectors
      QUEST_CHECKBOX: '.quest-checkbox',
      NESTED_TAB: '.nested-tab',
      NESTED_CONTENT: '.nested-content',
      // Back to Top selectors
      BTT_BUTTON: '.btt-button'
    },
     CLASSES: {
     CLASSES: {
      ACTIVE: 'active',
      EXPANDED: 'expanded',
      COLLAPSED: 'collapsed',
      MOBILE_OPEN: 'mobile-open',
      SIDEBAR_OPEN: 'sidebar-open',
      RECOMMENDED: 'recommended',
      // Back to Top classes
       SHOW: 'show',
       SHOW: 'show',
       PULSE: 'pulse'
       PULSE: 'pulse'
    },
    STORAGE_KEYS: {
      SIDEBAR_PREFIX: 'sidebar-',
      USER_MENU_STATE: 'user-menu-state',
      QUEST_PROGRESS: 'shamanJiaQuestProgress',
      USER_THEME: 'mw-user-theme'
    },
    DELAYS: {
      SCROLL_DELAY: 100,
      ACCORDION_SCROLL_DELAY: 300
    }
  };
  // ============================================================================
  // UTILITY FUNCTIONS
  // ============================================================================
  /**
  * Safely gets an element by ID with error handling
  * @param {string} id - The element ID
  * @returns {Element|null} The element or null if not found
  */
  function safeGetElementById(id) {
    try {
      return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }
  /**
  * Safely queries for elements with error handling
  * @param {string} selector - CSS selector
  * @param {Element} [context=document] - Context element
  * @returns {NodeList} NodeList of matching elements
  */
  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }
  /**
  * Safely queries for a single element with error handling
  * @param {string} selector - CSS selector
  * @param {Element} [context=document] - Context element
  * @returns {Element|null} The first matching element or null
  */
  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
  }
  /**
  * Safely accesses localStorage with error handling
  * @param {string} key - Storage key
  * @param {string} [defaultValue=''] - Default value if key doesn't exist
  * @returns {string} The stored value or default
  */
  function safeGetLocalStorage(key, defaultValue = '') {
    try {
      return localStorage.getItem(key) || defaultValue;
    } catch (error) {
      console.warn(`localStorage access failed for key '${key}':`, error);
      return defaultValue;
    }
  }
  /**
  * Safely sets localStorage with error handling
  * @param {string} key - Storage key
  * @param {string} value - Value to store
  * @returns {boolean} Success status
  */
  function safeSetLocalStorage(key, value) {
    try {
      localStorage.setItem(key, value);
      return true;
    } catch (error) {
      console.warn(`localStorage write failed for key '${key}':`, error);
      return false;
    }
  }
  /**
  * Checks if an element is in the viewport
  * @param {Element} element - Element to check
  * @returns {boolean} True if element is visible in viewport
  */
  function isElementInViewport(element) {
    if (!element) return false;
   
    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  }
  /**
  * Determines if a node is inside an excluded container
  * @param {Node} node - The node to check
  * @returns {boolean} True if node is in excluded context
  */
  function isInExcludedContext(node) {
    if (!node || !node.parentElement) return false;
   
    const excludedSelectors = [
      'script', 'style', 'textarea', 'input', 'select', 'option',
      '.ve-ui-surface', '.mw-editform', '.CodeMirror', '.cm-editor', '.ace_editor'
    ].join(', ');
   
    return !!node.parentElement.closest(excludedSelectors);
  }
  const ThemeManager = {
    key: CONFIG.STORAGE_KEYS.USER_THEME,
    current: 'light',
    prefersDark: function() {
      return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    },
    load: function() {
      const v = safeGetLocalStorage(this.key);
      this.current = v || (this.prefersDark() ? 'dark' : 'light');
      this.apply(this.current);
    },
    apply: function(theme) {
      this.current = theme === 'dark' ? 'dark' : 'light';
      const root = document.documentElement;
      root.setAttribute('data-theme', this.current);
      document.body.classList.toggle('theme-dark', this.current === 'dark');
      safeSetLocalStorage(this.key, this.current);
      this.updateToggleUI();
    },
    toggle: function() {
      this.apply(this.current === 'dark' ? 'light' : 'dark');
    },
    mountToggle: function() {
      if (document.getElementById('pt-theme-toggle')) return;
      const render = () => {
        const li = (window.mw && window.mw.util)
          ? mw.util.addPortletLink('p-personal', '#', '', 'pt-theme-toggle', 'Alternar tema')
          : null;
        let anchor = li ? li.querySelector('a') : null;
        if (!anchor) {
          anchor = document.createElement('a');
          anchor.id = 'pt-theme-toggle';
          anchor.href = '#';
          (document.body || document.documentElement).appendChild(anchor);
        }
        anchor.setAttribute('role', 'button');
        anchor.setAttribute('aria-pressed', this.current === 'dark' ? 'true' : 'false');
        anchor.setAttribute('aria-label', this.current === 'dark' ? 'Tema escuro' : 'Tema claro');
        anchor.setAttribute('data-theme-toggle', 'true');
        anchor.innerHTML = this.iconMarkup();
        this.ensureDelegatedListener();
      };
      if (window.mw && mw.loader) {
        mw.loader.using(['mediawiki.util']).then(render, render);
      } else {
        render();
      }
    },
    ensureDelegatedListener: function() {
      if (this.boundDocClick) return;
      this.boundDocClick = (e) => {
        const t = e.target.closest('[data-theme-toggle]');
        if (!t) return;
        e.preventDefault();
        t.classList.add('pressed');
        setTimeout(() => t.classList.remove('pressed'), 150);
        this.toggle();
      };
      document.addEventListener('click', this.boundDocClick, false);
    },
    updateToggleUI: function() {
      let anchor = document.querySelector('[data-theme-toggle]');
      if (!anchor) {
        const li = document.getElementById('pt-theme-toggle');
        if (li) {
          if (li.tagName === 'A') anchor = li; else anchor = li.querySelector('a');
        }
      }
      if (!anchor) return;
      anchor.setAttribute('aria-pressed', this.current === 'dark' ? 'true' : 'false');
      anchor.setAttribute('aria-label', this.current === 'dark' ? 'Tema escuro' : 'Tema claro');
      anchor.innerHTML = this.iconMarkup();
    },
    iconMarkup: function() {
      const sun = '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12 18a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm0-10a4 4 0 1 1 0 8 4 4 0 0 1 0-8zm0-6h1v3h-1V2zm0 17h1v3h-1v-3zM2 11h3v1H2v-1zm17 0h3v1h-3v-1zM4.22 4.22l.7-.7 2.12 2.12-.7.7L4.22 4.22zm12.74 12.74.7-.7 2.12 2.12-.7.7-2.12-2.12zM4.22 19.78l2.12-2.12.7.7-2.12 2.12-.7-.7zm12.74-12.74 2.12-2.12.7.7-2.12 2.12-.7-.7z"/></svg>';
      const moon = '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M21 12.79A9 9 0 1 1 11.21 3a7 7 0 0 0 9.79 9.79z"/></svg>';
      return '<span class="theme-toggle-icon">'+(this.current==='dark'?moon:sun)+'</span>';
    },
    selfTest: function(times = 5, delay = 150) {
      const el = document.querySelector('[data-theme-toggle]');
      let i = 0;
      const run = () => {
        if (!el || i >= times) { console.log('Theme toggle test complete'); return; }
        el.click(); i++; setTimeout(run, delay);
      };
      run();
    },
    selfCheckLinks: function() {
      const theme = document.documentElement.getAttribute('data-theme');
      const report = (sel) => {
        const el = document.querySelector(sel);
        if (!el) return { selector: sel, present: false };
        const cs = getComputedStyle(el);
        return { selector: sel, present: true, color: cs.color };
      };
      const samples = [
        '#mw-content-text a',
        'body.skin-vector-legacy #mw-panel .vector-menu-content a',
        'body.skin-vector-legacy #p-personal a'
      ].map(report);
      console.table({ theme, sample0: samples[0], sample1: samples[1], sample2: samples[2] });
    },
    init: function() {
      this.load();
      this.mountToggle();
      this.ensureDelegatedListener();
    }
  };
  // ============================================================================
  // QUEST TRACKER MODULE (Integrated from ShamanGia)
  // ============================================================================
  /**
  * Quest Tracker Module
  * Manages quest progress tracking with localStorage persistence
  */
  const QuestTracker = {
    /**
    * Initializes quest tracker functionality
    */
    init: function() {
      // Only initialize if quest tracker elements exist
      const questCheckboxes = safeQuerySelectorAll(CONFIG.SELECTORS.QUEST_CHECKBOX);
      if (questCheckboxes.length === 0) {
        return;
      }
      this.loadQuestProgress();
      this.setupEventListeners();
      this.updateProgressDisplay();
     
      console.log('Quest Tracker initialized');
    },
    /**
    * Sets up event listeners for quest tracker
    */
    setupEventListeners: function() {
      const questCheckboxes = safeQuerySelectorAll(CONFIG.SELECTORS.QUEST_CHECKBOX);
      const resetButton = safeGetElementById('reset-progress');
     
      // Add event listeners to checkboxes
      questCheckboxes.forEach(checkbox => {
        checkbox.addEventListener('change', () => {
          this.saveQuestProgress();
          this.updateProgressDisplay();
        });
      });
     
      // Add event listener to reset button
      if (resetButton) {
        resetButton.addEventListener('click', () => {
          if (confirm('Are you sure you want to reset all quest progress? This action cannot be undone.')) {
            this.resetAllProgress();
          }
        });
      }
    },
    /**
    * Loads quest progress from localStorage
    */
    loadQuestProgress: function() {
      const savedProgress = safeGetLocalStorage(CONFIG.STORAGE_KEYS.QUEST_PROGRESS);
      if (savedProgress) {
        try {
          const progressData = JSON.parse(savedProgress);
          Object.keys(progressData).forEach(questId => {
            const checkbox = safeQuerySelector(`[data-quest="${questId}"]`);
            if (checkbox) {
              checkbox.checked = progressData[questId];
            }
          });
        } catch (error) {
          console.error('Error loading quest progress:', error);
        }
      }
    },
    /**
    * Saves quest progress to localStorage
    */
    saveQuestProgress: function() {
      const questCheckboxes = safeQuerySelectorAll(CONFIG.SELECTORS.QUEST_CHECKBOX);
      const progressData = {};
     
      questCheckboxes.forEach(checkbox => {
        const questId = checkbox.getAttribute('data-quest');
        if (questId) {
          progressData[questId] = checkbox.checked;
        }
      });
     
      try {
        safeSetLocalStorage(CONFIG.STORAGE_KEYS.QUEST_PROGRESS, JSON.stringify(progressData));
      } catch (error) {
        console.error('Error saving quest progress:', error);
      }
    },
    /**
    * Updates progress display elements
    */
    updateProgressDisplay: function() {
      const questCheckboxes = safeQuerySelectorAll(CONFIG.SELECTORS.QUEST_CHECKBOX);
      const totalQuests = questCheckboxes.length;
      let completedQuests = 0;
     
      questCheckboxes.forEach(checkbox => {
        if (checkbox.checked) {
          completedQuests++;
        }
      });
     
      const completionPercentage = totalQuests > 0 ? Math.round((completedQuests / totalQuests) * 100) : 0;
     
      // Update progress display elements
      const totalProgressElement = safeGetElementById('total-progress');
      const completionPercentageElement = safeGetElementById('completion-percentage');
      const progressFillElement = safeGetElementById('progress-fill');
     
      if (totalProgressElement) {
        totalProgressElement.textContent = `${completedQuests}/${totalQuests}`;
      }
     
      if (completionPercentageElement) {
        completionPercentageElement.textContent = `${completionPercentage}%`;
      }
     
      if (progressFillElement) {
        progressFillElement.style.width = `${completionPercentage}%`;
      }
    },
    /**
    * Resets all quest progress
    */
    resetAllProgress: function() {
      const questCheckboxes = safeQuerySelectorAll(CONFIG.SELECTORS.QUEST_CHECKBOX);
     
      // Uncheck all checkboxes
      questCheckboxes.forEach(checkbox => {
        checkbox.checked = false;
      });
     
      // Clear localStorage
      try {
        localStorage.removeItem(CONFIG.STORAGE_KEYS.QUEST_PROGRESS);
      } catch (error) {
        console.error('Error clearing quest progress:', error);
      }
     
      // Update progress display
      this.updateProgressDisplay();
    }
  };
  // ============================================================================
  // TAB NAVIGATION SYSTEM (Enhanced with Quest Tracker support)
  // ============================================================================
  /**
  * Tab Navigation Module
  * Handles tab switching functionality with smooth scrolling support
  * Enhanced to work with both MediaWiki tabs and Quest Tracker nested tabs
  */
  const TabNavigation = {
    /**
    * Shows a specific tab and hides others
    * @param {string} tabId - ID of the tab content to show
    * @param {Element} [tabElement] - The tab button element
    */
    showTab: function(tabId, tabElement) {
      if (!tabId) {
        console.warn('TabNavigation.showTab: tabId is required');
        return;
      }
      // CRITICAL FIX: Hide ALL nested contents first when switching main tabs
      // This prevents nested content from other sections remaining visible
      const allNestedContents = document.querySelectorAll('.nested-content');
      allNestedContents.forEach(content => {
        content.classList.remove(CONFIG.CLASSES.ACTIVE);
      });
      // Hide all tab contents
      const contents = safeQuerySelectorAll(CONFIG.SELECTORS.TAB_CONTENT);
      contents.forEach(content => content.classList.remove(CONFIG.CLASSES.ACTIVE));
      // Deactivate all tab buttons
      const tabs = safeQuerySelectorAll(CONFIG.SELECTORS.NAV_TAB);
      tabs.forEach(tab => tab.classList.remove(CONFIG.CLASSES.ACTIVE));
      // Show target tab content
      const targetContent = safeGetElementById(tabId);
      if (targetContent) {
        targetContent.classList.add(CONFIG.CLASSES.ACTIVE);
       
        // If this tab has nested tabs, activate the first nested tab
        if (tabId === 'tab-normal-daily' || tabId === 'tab-shadow-dailies' || tabId === 'tab-shaman-girl-jia') {
          setTimeout(() => {
            const firstNestedTab = targetContent.querySelector('.nested-tab');
            if (firstNestedTab) {
              const firstNestedTabId = firstNestedTab.getAttribute('data-tab');
              if (firstNestedTabId) {
                TabNavigation.showNestedTab(firstNestedTabId, firstNestedTab);
              }
            }
          }, 50);
        }
      } else {
        console.warn(`Tab content with ID '${tabId}' not found`);
      }
      // Activate the clicked tab button
      if (tabElement) {
        tabElement.classList.add(CONFIG.CLASSES.ACTIVE);
      }
    },
    /**
    * Shows a specific nested tab and hides others
    * @param {string} tabId - ID of the nested tab content to show
    * @param {Element} [tabElement] - The nested tab button element
    */
    showNestedTab: function(tabId, tabElement) {
      if (!tabId) {
        console.warn('TabNavigation.showNestedTab: tabId is required');
        return;
      }
      const container = tabElement
        ? (tabElement.closest('.nested-container, .token-card')
            || tabElement.closest('.tokens-section')
            || tabElement.closest('.tab-content')
            || document)
        : document;
      const nestedContents = container.querySelectorAll('.nested-content');
      nestedContents.forEach(content => {
        content.classList.remove(CONFIG.CLASSES.ACTIVE);
      });
      const nestedTabs = container.querySelectorAll('.nested-tab');
      nestedTabs.forEach(tab => {
        tab.classList.remove(CONFIG.CLASSES.ACTIVE);
      });
      let targetContent = null;
      if (container && container.querySelector) {
        targetContent = container.querySelector('#' + tabId);
      }
      if (!targetContent) {
        targetContent = safeGetElementById(tabId);
      }
      if (targetContent) {
        targetContent.classList.add(CONFIG.CLASSES.ACTIVE);
      } else {
        console.warn(`Nested tab content with ID '${tabId}' not found`);
      }
      if (tabElement) {
        tabElement.classList.add(CONFIG.CLASSES.ACTIVE);
      }
    },
    /**
    * Handles tab click events using event delegation
    * @param {Event} event - Click event
    */
    handleTabClick: function(event) {
      const tab = event.target.closest('.nav-tab[data-tab], .nav-tab-fgod[data-tab]');
      if (!tab) return;
      event.preventDefault();
      const targetId = tab.getAttribute('data-tab');
     
      if (targetId) {
        TabNavigation.showTab(targetId, tab);
      }
    },
    /**
    * Handles nested tab click events using event delegation
    * Enhanced to work with Quest Tracker nested tabs
    * @param {Event} event - Click event
    */
    handleNestedTabClick: function(event) {
      const nestedTab = event.target.closest('.nested-tab[data-tab]');
      if (!nestedTab) return;
      event.preventDefault();
      const targetId = nestedTab.getAttribute('data-tab');
     
      if (targetId) {
        TabNavigation.showNestedTab(targetId, nestedTab);
      }
    },
    /**
    * Handles special navigation clicks with scroll support
    * @param {Event} event - Click event
    */
    handleSpecialTabNavigation: function(event) {
      const trigger = event.target.closest('[data-tab-trigger]');
      if (!trigger) return;
      event.preventDefault();
     
      const tabData = trigger.getAttribute('data-tab-trigger');
      const buttonId = trigger.getAttribute('data-tab-button');
      const href = trigger.getAttribute('href');
      const scrollToAttr = trigger.getAttribute('data-scroll-to');
      // Determine scroll target
      let scrollTargetSelector = null;
      if (scrollToAttr && scrollToAttr.trim()) {
        scrollTargetSelector = '#' + scrollToAttr.replace(/^#/, '');
      } else if (href && href.charAt(0) === '#') {
        scrollTargetSelector = href;
      }
      // Switch tab if specified
      if (tabData && buttonId) {
        const targetButton = safeGetElementById(buttonId);
        if (targetButton) {
          TabNavigation.showTab(tabData, targetButton);
         
          // Scroll to anchor after tab switch
          if (scrollTargetSelector) {
            const scrollTarget = safeQuerySelector(scrollTargetSelector);
            if (scrollTarget) {
              setTimeout(() => {
                scrollTarget.scrollIntoView({ behavior: 'smooth' });
              }, CONFIG.DELAYS.SCROLL_DELAY);
            }
          }
        }
      }
    },
    /**
    * Initializes tab navigation event listeners
    */
    init: function() {
      document.addEventListener('click', this.handleTabClick, false);
      document.addEventListener('click', this.handleNestedTabClick, false);
      document.addEventListener('click', this.handleSpecialTabNavigation, false);
     
      // Make showTab globally available for backward compatibility
      window.showTab = this.showTab;
     }
     }
   };
   };


  // ============================================================================
  // BACK TO TOP MODULE
  // ============================================================================
  /**
  * Back to Top Module
  * Manages the back-to-top button functionality with smooth animations
  */
   const BackToTop = {
   const BackToTop = {
     button: null,
     button: null,
     isVisible: false,
     isVisible: false,
     scrollThreshold: 300,
     scrollThreshold: 300,
   
 
    /**
     createButton: function () {
    * Creates the back-to-top button element
    */
     createButton: function() {
       const button = document.createElement('button');
       const button = document.createElement('button');
       button.className = 'btt-button';
       button.className = 'btt-button';
Line 650: Line 30:
     },
     },


    /**
     showButton: function () {
    * Shows the back-to-top button with animation
    */
     showButton: function() {
       if (!this.isVisible && this.button) {
       if (!this.isVisible && this.button) {
         this.button.classList.add(CONFIG.CLASSES.SHOW);
         this.button.classList.add(CONFIG.CLASSES.SHOW);
         this.button.classList.add(CONFIG.CLASSES.PULSE);
         this.button.classList.add(CONFIG.CLASSES.PULSE);
         this.isVisible = true;
         this.isVisible = true;
       
 
        // Remove pulse class after animation
         setTimeout(() => {
         setTimeout(() => {
           if (this.button) {
           if (this.button) {
Line 668: Line 44:
     },
     },


    /**
     hideButton: function () {
    * Hides the back-to-top button with animation
    */
     hideButton: function() {
       if (this.isVisible && this.button) {
       if (this.isVisible && this.button) {
         this.button.classList.remove(CONFIG.CLASSES.SHOW);
         this.button.classList.remove(CONFIG.CLASSES.SHOW);
Line 679: Line 52:
     },
     },


    /**
     handleScroll: function () {
    * Handles scroll events to show/hide button
    */
     handleScroll: function() {
       const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
       const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
     
 
       if (scrollTop > this.scrollThreshold) {
       if (scrollTop > this.scrollThreshold) {
         this.showButton();
         this.showButton();
Line 692: Line 62:
     },
     },


    /**
     handleClick: function () {
    * Handles button click to scroll to top
    */
     handleClick: function() {
       window.scrollTo({
       window.scrollTo({
         top: 0,
         top: 0,
Line 702: Line 69:
     },
     },


    /**
     init: function () {
    * Initializes the back-to-top functionality
    */
     init: function() {
      // Create the button
       this.button = this.createButton();
       this.button = this.createButton();
     
 
      // Bind event handlers
       const boundHandleScroll = this.handleScroll.bind(this);
       const boundHandleScroll = this.handleScroll.bind(this);
       const boundHandleClick = this.handleClick.bind(this);
       const boundHandleClick = this.handleClick.bind(this);
     
 
      // Add event listeners
       window.addEventListener('scroll', boundHandleScroll, { passive: true });
       window.addEventListener('scroll', boundHandleScroll, { passive: true });
       this.button.addEventListener('click', boundHandleClick);
       this.button.addEventListener('click', boundHandleClick);
     
 
      // Initial check
       this.handleScroll();
       this.handleScroll();
     
 
       console.log('Back to Top button initialized');
       console.log('Back to Top button initialized');
     }
     }
   };
   };


   // ============================================================================
   if (document.readyState === 'loading') {
   // NAVIGATION & INTERACTION HANDLERS (Enhanced)
    document.addEventListener('DOMContentLoaded', BackToTop.init.bind(BackToTop));
   // ============================================================================
  } else {
    BackToTop.init();
   }
})();
 
/*
Propósito da funcionalidade:
- Permitir navegação por clique em “cards” que tenham atributo `data-link`.
- Suportar três tipos de destino:
  - Âncoras internas (ex.: `#secao`) com rolagem suave.
   - URLs externas absolutas (`http://` ou `https://`).
  - Títulos de páginas MediaWiki, resolvidos via `mw.util.getUrl()` quando disponível.
*/
 
(function () {
  'use strict';
 
  const CONFIG = {
    SELECTORS: {
      CARD_LINKS: '.card[data-link], .destaque-card[data-link]'
    }
  };
 
  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
  }


  /**
  * Navigation Handlers Module
  * Manages various navigation interactions
  */
   const NavigationHandlers = {
   const NavigationHandlers = {
    /**
     handleCardClick: function (event) {
    * Handles card click-through navigation
    * @param {Event} event - Click event
    */
     handleCardClick: function(event) {
       const card = event.target.closest(CONFIG.SELECTORS.CARD_LINKS);
       const card = event.target.closest(CONFIG.SELECTORS.CARD_LINKS);
       if (!card) return;
       if (!card) return;
Line 744: Line 126:
       if (!link) return;
       if (!link) return;


      // Handle in-page anchor navigation
       if (link.charAt(0) === '#') {
       if (link.charAt(0) === '#') {
         event.preventDefault();
         event.preventDefault();
Line 754: Line 135:
       }
       }


      // Handle absolute external URLs
       if (/^https?:\/\//i.test(link)) {
       if (/^https?:\/\//i.test(link)) {
         window.location.href = link;
         window.location.href = link;
Line 760: Line 140:
       }
       }


      // Navigate to MediaWiki page
       const targetUrl = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
       const targetUrl = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
         ? window.mw.util.getUrl(link)
         ? window.mw.util.getUrl(link)
         : ('index.php?title=' + encodeURIComponent(link));
         : ('index.php?title=' + encodeURIComponent(link));
     
 
       window.location.href = targetUrl;
       window.location.href = targetUrl;
     },
     },


    /**
     init: function () {
    * Initializes navigation event handlers
    */
     init: function() {
       document.addEventListener('click', this.handleCardClick, false);
       document.addEventListener('click', this.handleCardClick, false);
     }
     }
   };
   };


   // ============================================================================
   if (document.readyState === 'loading') {
   // WIKI LINK PROCESSOR
    document.addEventListener('DOMContentLoaded', NavigationHandlers.init.bind(NavigationHandlers));
   // ============================================================================
   } else {
    NavigationHandlers.init();
   }
})();


  /**
/*
  * Wiki Link Processor Module
Propósito da funcionalidade:
  * Converts [[Title]] markup to proper MediaWiki links
- Criar um menu “hamburger” para abrir/fechar a sidebar no skin `vector-legacy`.
  */
- Alternar classes (`mobile-open`, `sidebar-open`, `active`) para controlar layout em mobile.
  const WikiLinkProcessor = {
- Fechar a sidebar ao clicar fora do painel ou ao pressionar `Escape`.
    /**
*/
    * Processes MediaWiki-style internal link markup
    * Converts [[Title]] or [[Title|Display]] to proper anchor tags
    */
    processWikiLinks: function() {
      // Skip if no raw brackets found (already processed by MediaWiki)
      if (!document.body || document.body.textContent.indexOf('[[') === -1) {
        return;
      }


      const walker = document.createTreeWalker(
(function () {
        document.body,
  'use strict';
        NodeFilter.SHOW_TEXT,
        null,
        false
      );


      const textNodes = [];
  const CONFIG = {
      let node;
    CLASSES: {
        
       ACTIVE: 'active',
       // Collect text nodes containing wiki links
       MOBILE_OPEN: 'mobile-open',
       while ((node = walker.nextNode())) {
       SIDEBAR_OPEN: 'sidebar-open'
        const value = node.nodeValue;
    }
        if (!value || value.indexOf('[[') === -1 || isInExcludedContext(node)) {
  };
          continue;
        }
        textNodes.push(node);
      }


       const linkRegex = /\[\[([^\|\]]+)(?:\|([^\]]+))?\]\]/g;
  function safeGetElementById(id) {
    try {
       return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }


      // Process each text node
  function safeQuerySelector(selector, context = document) {
      textNodes.forEach(textNode => {
    try {
        const sourceText = textNode.nodeValue;
      return context.querySelector(selector);
        if (!linkRegex.test(sourceText)) return;
    } catch (error) {
       
      console.warn(`Invalid selector '${selector}':`, error);
        linkRegex.lastIndex = 0; // Reset regex state
      return null;
    }
  }


        const parent = textNode.parentNode;
  const MobileInterface = {
        const fragment = document.createDocumentFragment();
    initialized: false,
        let lastIndex = 0;
        let match;


        while ((match = linkRegex.exec(sourceText)) !== null) {
    getPanel: function () {
          // Add text before the match
      return safeGetElementById('mw-panel');
          if (match.index > lastIndex) {
    },
            fragment.appendChild(
              document.createTextNode(sourceText.slice(lastIndex, match.index))
            );
          }
 
          const rawTitle = (match[1] || '').trim();
          const displayText = (match[2] != null ? match[2] : rawTitle).trim();
 
          // Skip File: or Image: links - keep original text
          if (/^(File:|Image:)/i.test(rawTitle)) {
            fragment.appendChild(document.createTextNode(match[0]));
          } else {
            // Remove leading colon if present
            const cleanTitle = rawTitle.charAt(0) === ':' ? rawTitle.slice(1) : rawTitle;
           
            // Create proper MediaWiki link
            const href = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
              ? window.mw.util.getUrl(cleanTitle)
              : ('index.php?title=' + encodeURIComponent(cleanTitle));
           
            const anchor = document.createElement('a');
            anchor.className = 'mw-link-internal';
            anchor.setAttribute('href', href);
            anchor.appendChild(document.createTextNode(displayText));
            fragment.appendChild(anchor);
          }
 
          lastIndex = linkRegex.lastIndex;
        }
 
        // Add remaining text
        if (lastIndex < sourceText.length) {
          fragment.appendChild(
            document.createTextNode(sourceText.slice(lastIndex))
          );
        }


        // Replace the original text node
    isApplicable: function () {
        parent.insertBefore(fragment, textNode);
      return document.body.classList.contains('skin-vector-legacy') && !!this.getPanel();
        parent.removeChild(textNode);
      });
     },
     },


     /**
     createHamburgerMenu: function () {
    * Initializes wiki link processing
       if (this.initialized) return;
    */
      if (!this.isApplicable()) {
    init: function() {
         return;
       if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', this.processWikiLinks);
      } else {
         this.processWikiLinks();
       }
       }
    }
  };
  // ============================================================================
  // MOBILE INTERFACE
  // ============================================================================


  /**
       if (document.querySelector('.mobile-hamburger-menu')) {
  * Mobile Interface Module
        this.initialized = true;
  * Handles mobile-specific UI components
  */
  const MobileInterface = {
    /**
    * Creates hamburger menu for mobile sidebar navigation
    */
    createHamburgerMenu: function() {
      // Only create for Vector Legacy skin
       if (!document.body.classList.contains('skin-vector-legacy')) {
         return;
         return;
       }
       }


       const panel = safeGetElementById('mw-panel');
       const panel = this.getPanel();
       if (!panel) return;
       if (!panel) return;


      // Create hamburger button
       const hamburger = document.createElement('button');
       const hamburger = document.createElement('button');
       hamburger.className = 'mobile-hamburger-menu';
       hamburger.className = 'mobile-hamburger-menu';
       hamburger.setAttribute('aria-label', 'Toggle navigation menu');
       hamburger.setAttribute('aria-label', 'Toggle navigation menu');
      hamburger.setAttribute('type', 'button');
       hamburger.innerHTML = '<span></span><span></span><span></span>';
       hamburger.innerHTML = '<span></span><span></span><span></span>';


      // Insert hamburger at top of page
       const content = safeGetElementById('content') || safeQuerySelector('.mw-body');
       const content = safeGetElementById('content') || safeQuerySelector('.mw-body');
       if (content && content.parentNode) {
       if (content && content.parentNode) {
Line 919: Line 231:
       }
       }


      // Toggle functionality
       const toggleSidebar = () => {
       const toggleSidebar = () => {
         panel.classList.toggle(CONFIG.CLASSES.MOBILE_OPEN);
         panel.classList.toggle(CONFIG.CLASSES.MOBILE_OPEN);
         hamburger.classList.toggle(CONFIG.CLASSES.ACTIVE);
         hamburger.classList.toggle(CONFIG.CLASSES.ACTIVE);
         document.body.classList.toggle(CONFIG.CLASSES.SIDEBAR_OPEN);
         document.body.classList.toggle(CONFIG.CLASSES.SIDEBAR_OPEN);
        try {
          const open = panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN);
          document.dispatchEvent(new CustomEvent('cora:mobile-sidebar-toggle', { detail: { open } }));
        } catch (e) {
          document.dispatchEvent(new Event('cora:mobile-sidebar-toggle'));
        }
       };
       };


      // Event listeners
       hamburger.addEventListener('click', (event) => {
       hamburger.addEventListener('click', (event) => {
         event.preventDefault();
         event.preventDefault();
Line 933: Line 249:
       });
       });


      // Close on outside click
       document.addEventListener('click', (event) => {
       document.addEventListener('click', (event) => {
         if (panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN) &&
         if (panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN) &&
            !panel.contains(event.target) &&
          !panel.contains(event.target) &&
            !hamburger.contains(event.target)) {
          !hamburger.contains(event.target)) {
           panel.classList.remove(CONFIG.CLASSES.MOBILE_OPEN);
           panel.classList.remove(CONFIG.CLASSES.MOBILE_OPEN);
           hamburger.classList.remove(CONFIG.CLASSES.ACTIVE);
           hamburger.classList.remove(CONFIG.CLASSES.ACTIVE);
Line 944: Line 259:
       });
       });


      // Close on escape key
       document.addEventListener('keydown', (event) => {
       document.addEventListener('keydown', (event) => {
         if (event.key === 'Escape' && panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN)) {
         if (event.key === 'Escape' && panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN)) {
Line 952: Line 266:
         }
         }
       });
       });
      this.initialized = true;
     },
     },


    /**
     init: function () {
    * Initializes mobile interface components
    */
     init: function() {
       this.createHamburgerMenu();
       this.createHamburgerMenu();
     }
     }
   };
   };


   // ============================================================================
   if (document.readyState === 'loading') {
   // COLLAPSIBLE SECTIONS
    document.addEventListener('DOMContentLoaded', MobileInterface.init.bind(MobileInterface));
  // ============================================================================
  } else {
    MobileInterface.init();
   }
})();
/*
Propósito da funcionalidade:
- Tornar os portais/menus laterais do skin `vector-legacy` colapsáveis.
- Colapsar automaticamente todas as seções do painel (exceto a primeira) ao iniciar.
- Persistir estado expandido/colapsado por seção via `localStorage` usando prefixo `sidebar-<id>`.
- Ajustar alturas (`maxHeight`) ao expandir/colapsar e recalcular em `resize`.
*/


  /**
(function () {
  * Collapsible Sections Module
  'use strict';
  * Manages expandable/collapsible content sections
 
  */
  const CONFIG = {
  const CollapsibleSections = {
    CLASSES: {
    /**
      COLLAPSED: 'collapsed'
    * Initializes collapsible header functionality
    */
    initCollapsibleHeaders: function() {
      const headers = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_HEADER);
     
      headers.forEach(header => {
        header.addEventListener('click', (event) => {
          event.preventDefault();
         
          const section = header.parentElement;
          const content = section.querySelector('.collapsible-content');
         
          if (content) {
            const isExpanded = section.classList.contains(CONFIG.CLASSES.EXPANDED);
           
            if (isExpanded) {
              section.classList.remove(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = '0';
            } else {
              section.classList.add(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = content.scrollHeight + 'px';
            }
          }
        });
      });
     },
     },
    STORAGE_KEYS: {
      SIDEBAR_PREFIX: 'sidebar-'
    }
  };
  function ensureSidebarStyles() {
    if (document.getElementById('cora-sidebar-collapsible-styles')) return;
    const style = document.createElement('style');
    style.id = 'cora-sidebar-collapsible-styles';
    style.textContent =
      'body.skin-vector-legacy #mw-panel .vector-menu-content,body.skin-vector-legacy #mw-panel .portal .body{overflow:hidden;transition:max-height .4s ease,opacity .3s ease;opacity:1}' +
      'body.skin-vector-legacy #mw-panel .vector-menu-portal.collapsed .vector-menu-content,body.skin-vector-legacy #mw-panel .portal.collapsed .body{max-height:0!important;opacity:0;padding:0}' +
      'body.skin-vector-legacy #mw-panel .vector-menu-portal .vector-menu-heading,body.skin-vector-legacy #mw-panel .portal h3{cursor:pointer;user-select:none}';
    document.head.appendChild(style);
  }


    /**
  function safeGetElementById(id) {
    * Initializes collapsible button functionality
    try {
    */
       return document.getElementById(id);
    initCollapsibleButtons: function() {
    } catch (error) {
       const buttons = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_BUTTON);
      console.warn(`Element with ID '${id}' not found:`, error);
     
      return null;
      buttons.forEach(button => {
    }
        button.addEventListener('click', (event) => {
  }
          event.preventDefault();
 
         
  function safeQuerySelectorAll(selector, context = document) {
          const content = button.nextElementSibling;
    try {
         
      return context.querySelectorAll(selector);
          if (content && content.classList.contains('collapsible-content')) {
    } catch (error) {
            const isActive = button.classList.contains(CONFIG.CLASSES.ACTIVE);
      console.warn(`Invalid selector '${selector}':`, error);
           
       return [];
            if (isActive) {
     }
              button.classList.remove(CONFIG.CLASSES.ACTIVE);
  }
              content.style.maxHeight = '0';
            } else {
              button.classList.add(CONFIG.CLASSES.ACTIVE);
              content.style.maxHeight = content.scrollHeight + 'px';
            }
          }
        });
       });
     },


     /**
  function safeGetLocalStorage(key, defaultValue = '') {
    * Initializes all collapsible section types
     try {
    */
      return localStorage.getItem(key) || defaultValue;
     init: function() {
     } catch (error) {
       this.initCollapsibleHeaders();
       console.warn(`localStorage access failed for key '${key}':`, error);
       this.initCollapsibleButtons();
       return defaultValue;
     }
     }
   };
   }


   // ============================================================================
   function safeSetLocalStorage(key, value) {
  // SIDEBAR MANAGEMENT
    try {
   // ============================================================================
      localStorage.setItem(key, value);
      return true;
    } catch (error) {
      console.warn(`localStorage write failed for key '${key}':`, error);
      return false;
    }
   }


  /**
  * Sidebar Management Module
  * Handles Vector Legacy sidebar collapsible functionality
  */
   const SidebarManager = {
   const SidebarManager = {
     /**
     portals: null,
    * Gets a unique portal ID for localStorage
     getPortalId: function (portal) {
    * @param {Element} portal - Portal element
    * @returns {string} Unique portal identifier
    */
     getPortalId: function(portal) {
       const heading = portal.querySelector('.vector-menu-heading, h3');
       const heading = portal.querySelector('.vector-menu-heading, h3');
       if (heading) {
       if (heading) {
Line 1,057: Line 359:
     },
     },


     /**
     syncExpandedHeights: function () {
    * Initializes sidebar collapsible functionality
      if (!this.portals) return;
    */
      this.portals.forEach(portal => {
     initSidebarCollapsible: function() {
        if (portal.classList.contains(CONFIG.CLASSES.COLLAPSED)) return;
      // Only for Vector Legacy skin
        const content = portal.querySelector('.vector-menu-content, .body');
        if (!content) return;
        content.style.maxHeight = content.scrollHeight + 'px';
        content.style.opacity = '1';
      });
    },
 
     initSidebarCollapsible: function () {
       if (!document.body.classList.contains('skin-vector-legacy')) {
       if (!document.body.classList.contains('skin-vector-legacy')) {
         return;
         return;
Line 1,068: Line 377:
       const panel = safeGetElementById('mw-panel');
       const panel = safeGetElementById('mw-panel');
       if (!panel) return;
       if (!panel) return;
      ensureSidebarStyles();


       const portals = safeQuerySelectorAll('.vector-menu-portal, .portal', panel);
       const portals = safeQuerySelectorAll('.vector-menu-portal, .portal', panel);
        
       this.portals = Array.from(portals);
      // Auto-collapse all sections except first (Navigation)
 
       portals.forEach((portal, index) => {
       portals.forEach((portal, index) => {
         if (index > 0) {
         if (index > 0) {
Line 1,078: Line 389:
           if (content) {
           if (content) {
             content.style.maxHeight = '0';
             content.style.maxHeight = '0';
            content.style.opacity = '0';
           }
           }
         }
         }
       });
       });


      // Add click handlers to headings
       const headings = safeQuerySelectorAll('.vector-menu-heading, .portal h3', panel);
       const headings = safeQuerySelectorAll('.vector-menu-heading, .portal h3', panel);
     
 
       headings.forEach(heading => {
       headings.forEach(heading => {
         heading.addEventListener('click', (event) => {
         heading.addEventListener('click', (event) => {
           event.preventDefault();
           event.preventDefault();
           event.stopPropagation();
           event.stopPropagation();
         
 
           const portal = heading.closest('.vector-menu-portal, .portal');
           const portal = heading.closest('.vector-menu-portal, .portal');
           if (!portal) return;
           if (!portal) return;
Line 1,100: Line 411:


           if (isCollapsed) {
           if (isCollapsed) {
            // Expand
             portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
             portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
             content.style.maxHeight = content.scrollHeight + 'px';
             content.style.maxHeight = content.scrollHeight + 'px';
            content.style.opacity = '1';
             safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'expanded');
             safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'expanded');
           } else {
           } else {
            // Collapse
             portal.classList.add(CONFIG.CLASSES.COLLAPSED);
             portal.classList.add(CONFIG.CLASSES.COLLAPSED);
             content.style.maxHeight = '0';
             content.style.maxHeight = '0';
            content.style.opacity = '0';
             safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'collapsed');
             safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'collapsed');
           }
           }
Line 1,113: Line 424:
       });
       });


      // Restore saved states
       portals.forEach((portal, index) => {
       portals.forEach((portal, index) => {
         if (index === 0) return; // Skip first portal
         if (index === 0) return;
       
 
         const savedState = safeGetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + this.getPortalId(portal));
         const savedState = safeGetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + this.getPortalId(portal));
         const content = portal.querySelector('.vector-menu-content, .body');
         const content = portal.querySelector('.vector-menu-content, .body');
       
 
         if (savedState === 'expanded' && content) {
         if (savedState === 'expanded' && content) {
           portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
           portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
           content.style.maxHeight = content.scrollHeight + 'px';
           content.style.maxHeight = content.scrollHeight + 'px';
          content.style.opacity = '1';
         }
         }
       });
       });


      // Recalculate heights on resize
       window.addEventListener('resize', () => {
       window.addEventListener('resize', () => {
         portals.forEach(portal => {
         this.syncExpandedHeights();
          if (!portal.classList.contains(CONFIG.CLASSES.COLLAPSED)) {
      });
            const content = portal.querySelector('.vector-menu-content, .body');
 
             if (content) {
      document.addEventListener('cora:mobile-sidebar-toggle', () => {
        this.syncExpandedHeights();
      });
    },
 
    init: function () {
      this.initSidebarCollapsible();
    }
  };
 
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', SidebarManager.init.bind(SidebarManager));
  } else {
    SidebarManager.init();
  }
})();
 
/*
Propósito da funcionalidade:
- Abrir imagens em um “lightbox” (overlay) ao clicar, permitindo ampliar e visualizar com foco.
- Fechar ao clicar no fundo do overlay ou ao pressionar `Escape`.
- Bloquear rolagem do body enquanto o overlay estiver aberto e restaurar ao fechar.
- Exibir legenda baseada em `.image-container .image-caption` ou no `alt` da imagem.
- Adaptar a cor do fundo e da legenda ao tema atual via atributo `data-theme` no `<html>`.
*/
 
(function () {
  'use strict';
 
  const ImageLightbox = {
    overlay: null,
    bodyOverflow: '',
    init: function () {
      document.addEventListener('click', this.handleDocumentClick.bind(this), false);
    },
    shouldIgnoreClick: function (target) {
      if (!target || !target.closest) return false;
      if (target.closest('a[href]')) return true;
      if (target.closest('.nav-tabs, .nav-tab, .nav-tab-fgod, .nested-tabs, .nested-tab')) return true;
      return false;
    },
    buildOverlay: function () {
      const overlay = document.createElement('div');
      overlay.className = 'image-lightbox-overlay';
      const theme = document.documentElement.getAttribute('data-theme') || 'light';
      const bg = theme === 'dark' ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.7)';
      overlay.style.position = 'fixed';
      overlay.style.top = '0';
      overlay.style.right = '0';
      overlay.style.bottom = '0';
      overlay.style.left = '0';
      overlay.style.display = 'flex';
      overlay.style.alignItems = 'center';
      overlay.style.justifyContent = 'center';
      overlay.style.background = bg;
      overlay.style.zIndex = '9999';
      overlay.style.padding = '2vw';
      overlay.style.cursor = 'zoom-out';
      overlay.setAttribute('role', 'dialog');
      overlay.setAttribute('aria-modal', 'true');
      overlay.addEventListener('click', (e) => { if (e.target === overlay) this.close(); });
      document.addEventListener('keydown', this.handleKeydown);
      return overlay;
    },
    handleKeydown: function (e) {
      if (e.key === 'Escape') {
        const self = ImageLightbox;
        if (self.overlay) { self.close(); }
      }
    },
    findImageFromTarget: function (target) {
      const el = target.closest('img, .image-container, .image-grid img');
      if (!el) return null;
      if (el.tagName && el.tagName.toLowerCase() === 'img') return el;
      const img = el.querySelector('img');
      return img || null;
    },
    handleDocumentClick: function (e) {
      if (this.overlay && this.overlay.contains(e.target)) return;
      if (this.shouldIgnoreClick(e.target)) return;
      const img = this.findImageFromTarget(e.target);
      if (!img) return;
      e.preventDefault();
      e.stopPropagation();
      this.open(img);
    },
    open: function (img) {
      if (this.overlay) return;
      this.overlay = this.buildOverlay();
      this.bodyOverflow = document.body.style.overflow || '';
      document.body.style.overflow = 'hidden';
      const content = document.createElement('div');
      content.style.position = 'relative';
      content.style.maxWidth = '90vw';
      content.style.maxHeight = '90vh';
      const clone = document.createElement('img');
      clone.src = img.currentSrc || img.src;
      clone.alt = img.alt || '';
      clone.style.maxWidth = '90vw';
      clone.style.maxHeight = '90vh';
      clone.style.borderRadius = '12px';
      clone.style.boxShadow = '0 10px 30px rgba(0,0,0,0.25)';
      clone.style.transform = 'scale(0.98)';
      clone.style.opacity = '0';
      clone.style.transition = 'transform 150ms ease, opacity 150ms ease';
      clone.style.willChange = 'transform, opacity';
      content.appendChild(clone);
      const captionText = this.getCaptionText(img);
      if (captionText) {
        const caption = document.createElement('div');
        caption.textContent = captionText;
        caption.style.marginTop = '0.75rem';
        caption.style.fontSize = '0.9rem';
        caption.style.textAlign = 'center';
        const theme = document.documentElement.getAttribute('data-theme') || 'light';
        caption.style.color = theme === 'dark' ? '#a8c6e8' : '#666';
        content.appendChild(caption);
      }
      this.overlay.appendChild(content);
      document.body.appendChild(this.overlay);
      requestAnimationFrame(() => {
        clone.style.transform = 'scale(1.25)';
        clone.style.opacity = '1';
      });
    },
    getCaptionText: function (img) {
      const container = img.closest('.image-container');
      const capEl = container ? container.querySelector('.image-caption') : null;
      if (capEl && capEl.textContent) {
        return capEl.textContent.trim();
      }
      return img.alt ? img.alt.trim() : '';
    },
    close: function () {
      if (!this.overlay) return;
      document.removeEventListener('keydown', this.handleKeydown);
      if (this.overlay.parentNode) this.overlay.parentNode.removeChild(this.overlay);
      this.overlay = null;
      document.body.style.overflow = this.bodyOverflow;
    }
  };
 
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', ImageLightbox.init.bind(ImageLightbox));
  } else {
    ImageLightbox.init();
  }
})();
 
( function () {
'use strict';
 
const CONFIG = {
CLASSES: {
EXPANDED: 'expanded'
},
STORAGE_KEYS: {
USER_MENU_STATE: 'user-menu-state',
PERSONAL_MENU_STATE: 'personal-menu-state'
},
BODY_CLASSES: {
HAS_USER_MENU: 'has-user-menu-collapsible',
HAS_PERSONAL_MENU: 'has-personal-menu-collapsible'
}
};
 
/**
* @typedef {Object} CollapsibleMenuOptions
* @property {string} containerId
* @property {string} containerClass
* @property {string} headerClass
* @property {string} contentClass
* @property {string} listClass
* @property {string} headerText
* @property {string} stateStorageKey
* @property {string} bodyClass
* @property {string[]} itemIds
*/
 
/**
* @param {string} id
* @return {HTMLElement|null}
*/
function safeGetElementById( id ) {
try {
return document.getElementById( id );
} catch ( error ) {
return null;
}
}
 
/**
* @param {string} key
* @param {string} [defaultValue]
* @return {string}
*/
function safeGetLocalStorage( key, defaultValue = '' ) {
try {
return localStorage.getItem( key ) || defaultValue;
} catch ( error ) {
return defaultValue;
}
}
 
/**
* @param {string} key
* @param {string} value
* @return {boolean}
*/
function safeSetLocalStorage( key, value ) {
try {
localStorage.setItem( key, value );
return true;
} catch ( error ) {
return false;
}
}
 
/**
* @param {CollapsibleMenuOptions} options
* @return {HTMLElement|null}
*/
function buildCollapsibleMenu( options ) {
const existing = safeGetElementById( options.containerId );
if ( existing ) {
return existing;
}
 
const items = options.itemIds
.map( ( id ) => safeGetElementById( id ) )
.filter( Boolean );
if ( items.length === 0 ) {
return null;
}
 
const menuContainer = document.createElement( 'div' );
menuContainer.id = options.containerId;
menuContainer.className = options.containerClass;
 
const menuHeader = document.createElement( 'div' );
menuHeader.className = options.headerClass;
menuHeader.textContent = options.headerText;
 
menuHeader.addEventListener( 'click', () => {
menuContainer.classList.toggle( CONFIG.CLASSES.EXPANDED );
 
const state = menuContainer.classList.contains( CONFIG.CLASSES.EXPANDED ) ?
'expanded' :
'collapsed';
safeSetLocalStorage( options.stateStorageKey, state );
} );
 
const menuContent = document.createElement( 'div' );
menuContent.className = options.contentClass;
 
const menuList = document.createElement( 'ul' );
menuList.className = options.listClass;
 
items.forEach( ( item ) => {
if ( item && item.parentNode ) {
menuList.appendChild( item );
}
} );
 
menuContent.appendChild( menuList );
menuContainer.appendChild( menuHeader );
menuContainer.appendChild( menuContent );
document.body.appendChild( menuContainer );
 
document.body.classList.add( options.bodyClass );
 
const savedState = safeGetLocalStorage( options.stateStorageKey );
if ( savedState === 'expanded' ) {
menuContainer.classList.add( CONFIG.CLASSES.EXPANDED );
}
 
document.addEventListener( 'click', ( event ) => {
const target = event.target;
if (
menuContainer.classList.contains( CONFIG.CLASSES.EXPANDED ) &&
target instanceof Node &&
!menuContainer.contains( target )
) {
menuContainer.classList.remove( CONFIG.CLASSES.EXPANDED );
safeSetLocalStorage( options.stateStorageKey, 'collapsed' );
}
} );
 
menuContainer.addEventListener( 'mouseenter', () => {
menuContainer.classList.add( CONFIG.CLASSES.EXPANDED );
} );
 
menuContainer.addEventListener( 'mouseleave', () => {
menuContainer.classList.remove( CONFIG.CLASSES.EXPANDED );
safeSetLocalStorage( options.stateStorageKey, 'collapsed' );
} );
 
return menuContainer;
}
 
const UserMenuManager = {
initialized: false,
 
initUserMenuCollapsible: function () {
if ( this.initialized ) {
return;
}
 
if ( !document.body.classList.contains( 'skin-vector-legacy' ) ) {
return;
}
 
const personalTools = safeGetElementById( 'p-personal' );
if ( !personalTools ) {
return;
}
 
const isLoggedIn = !!safeGetElementById( 'pt-userpage' );
const isAnonymous = !isLoggedIn && !!safeGetElementById( 'pt-login' );
 
if ( isLoggedIn ) {
buildCollapsibleMenu( {
containerId: 'user-menu-collapsible',
containerClass: 'user-menu-container',
headerClass: 'user-menu-header',
contentClass: 'user-menu-content',
listClass: 'user-menu-list',
headerText: 'User',
stateStorageKey: CONFIG.STORAGE_KEYS.USER_MENU_STATE,
bodyClass: CONFIG.BODY_CLASSES.HAS_USER_MENU,
itemIds: [
'pt-userpage', 'pt-mytalk', 'pt-preferences',
'pt-watchlist', 'pt-mycontris', 'pt-logout'
]
} );
}
 
if ( isAnonymous ) {
buildCollapsibleMenu( {
containerId: 'personal-menu-collapsible',
containerClass: 'personal-menu-container',
headerClass: 'personal-menu-header',
contentClass: 'personal-menu-content',
listClass: 'personal-menu-list',
headerText: 'User',
stateStorageKey: CONFIG.STORAGE_KEYS.PERSONAL_MENU_STATE,
bodyClass: CONFIG.BODY_CLASSES.HAS_PERSONAL_MENU,
itemIds: [
'pt-login', 'pt-createaccount', 'pt-anonuserpage', 'pt-anontalk'
]
} );
}
 
this.initialized = true;
},
 
init: function () {
this.initUserMenuCollapsible();
}
};
 
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', UserMenuManager.init.bind( UserMenuManager ) );
} else {
UserMenuManager.init();
}
}() );
 
/*-------------------------------------------------------------------------------------------------------------Link Processor------------------------------------------------------------*/
 
 
importScript('MediaWiki:MeuScript.js');
 
( function () {
'use strict';
 
function ensureThemeStylesLoaded() {
try {
if ( !window.mw || !mw.loader || !mw.config ) {
return;
}
 
const skin = mw.config.get( 'skin' ) || 'vector';
const moduleName = 'themeloader.skins.' + skin + '.default';
 
if ( typeof mw.loader.getState === 'function' ) {
const state = mw.loader.getState( moduleName );
if ( state === null ) {
return;
}
if ( state === 'ready' || state === 'loading' ) {
return;
}
}
 
mw.loader.load( moduleName );
} catch ( e ) {
// Ignore error
}
}
 
function getSavedTheme() {
try {
return localStorage.getItem( 'mw-theme' ) || 'default';
} catch ( e ) {
return 'default';
}
}
 
// Define available themes matching christmas.css roots
const themes = [
{ name: 'Pink', id: 'default' },
{ name: 'Dark', id: 'dark-neutral' },
{ name: 'Christmas', id: 'christmas' },
{ name: 'Power Light', id: 'power-light' },
{ name: 'Magic Light', id: 'magic-light' },
{ name: 'Sense Light', id: 'sense-light' },
{ name: 'Charm Light', id: 'charm-light' },
{ name: 'Power Dark', id: 'power-dark' },
{ name: 'Charm Dark', id: 'charm-dark' },
{ name: 'Magic Dark', id: 'dark-lightBlue' },
{ name: 'Sense Dark', id: 'dark-purple' },
];
 
// Function to apply theme
function applyTheme( themeId ) {
ensureThemeStylesLoaded();
 
try {
localStorage.setItem( 'mw-theme', themeId );
} catch ( e ) {
// Ignore error
}
 
if ( themeId === 'default' ) {
document.documentElement.removeAttribute( 'data-theme' );
} else {
document.documentElement.setAttribute( 'data-theme', themeId );
}
}
 
// Initialize theme immediately
ensureThemeStylesLoaded();
applyTheme( getSavedTheme() );
 
// Wait for DOM
const domReady = function ( callback ) {
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', callback );
} else {
setTimeout( callback, 0 );
}
};
 
domReady( function () {
// Prevent duplicate injection
if ( document.getElementById( 'mw-theme-floating' ) ) {
return;
}
 
// Create Floating Container
// ID matches CSS: #mw-theme-floating
const container = document.createElement( 'div' );
container.id = 'mw-theme-floating';
 
// Create Inner Wrapper
// Class matches CSS: .themeMenu
const themeMenuDiv = document.createElement( 'div' );
themeMenuDiv.className = 'themeMenu';
 
// Create Toggle Button
// Class matches CSS: .themeMenu-toggle
const btn = document.createElement( 'button' );
btn.className = 'themeMenu-toggle';
btn.textContent = 'Appearence';
btn.title = 'Change appearence';
btn.type = 'button';
// Create Dropdown List
// Class matches CSS: .themeMenu-dropdown
const dropdown = document.createElement( 'ul' );
dropdown.className = 'themeMenu-dropdown';
// Populate Items
themes.forEach( function ( theme ) {
// Item Wrapper
// CSS doesn't strictly require this wrapper for floating,
// but theme-menu.css might use .themeMenu-itemWrap
const itemWrap = document.createElement( 'li' );
itemWrap.className = 'themeMenu-itemWrap';
 
const themeLink = document.createElement( 'a' );
themeLink.className = 'themeMenu-item';
themeLink.href = '#';
themeLink.textContent = theme.name;
themeLink.dataset.themeId = theme.id;
// Highlight current
const currentTheme = getSavedTheme();
if ( theme.id === currentTheme ) {
themeLink.classList.add( 'is-current' );
}
themeLink.addEventListener( 'click', function ( e ) {
e.preventDefault();
ensureThemeStylesLoaded();
applyTheme( theme.id );
// Update highlighting
dropdown.querySelectorAll( '.themeMenu-item' ).forEach( function ( link ) {
link.classList.remove( 'is-current' );
} );
themeLink.classList.add( 'is-current' );
 
// Close menu
themeMenuDiv.classList.remove( 'is-open' );
} );
 
itemWrap.appendChild( themeLink );
dropdown.appendChild( itemWrap );
} );
 
// Toggle Logic
// CSS uses .themeMenu.is-open .themeMenu-dropdown { display: block }
btn.addEventListener( 'click', function ( e ) {
e.preventDefault();
e.stopPropagation();
themeMenuDiv.classList.toggle( 'is-open' );
} );
 
// Click Outside Logic
document.addEventListener( 'click', function ( e ) {
if ( !themeMenuDiv.contains( e.target ) ) {
themeMenuDiv.classList.remove( 'is-open' );
}
} );
 
// Assemble
themeMenuDiv.appendChild( btn );
themeMenuDiv.appendChild( dropdown );
container.appendChild( themeMenuDiv );
 
// Inject into Body
document.body.appendChild( container );
} );
 
}() );
 
 
 
(function () {
  'use strict';
 
  const CONFIG = {
    SELECTORS: {
      COLLAPSIBLE_HEADER: '.collapsible-header',
      COLLAPSIBLE_BUTTON: '.collapsible'
    },
    CLASSES: {
      ACTIVE: 'active',
      EXPANDED: 'expanded'
    }
  };
 
  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }
 
  const CollapsibleSections = {
    initCollapsibleHeaders: function () {
      const headers = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_HEADER);
 
      headers.forEach(header => {
        header.addEventListener('click', (event) => {
          event.preventDefault();
 
          const section = header.parentElement;
          const content = section.querySelector('.collapsible-content');
 
          if (content) {
            const isExpanded = section.classList.contains(CONFIG.CLASSES.EXPANDED);
 
             if (isExpanded) {
              section.classList.remove(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = '0';
            } else {
              section.classList.add(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = content.scrollHeight + 'px';
            }
          }
        });
      });
    },
 
    initCollapsibleButtons: function () {
      const buttons = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_BUTTON);
 
      buttons.forEach(button => {
        button.addEventListener('click', (event) => {
          event.preventDefault();
 
          const content = button.nextElementSibling;
 
          if (content && content.classList.contains('collapsible-content')) {
            const isActive = button.classList.contains(CONFIG.CLASSES.ACTIVE);
 
            if (isActive) {
              button.classList.remove(CONFIG.CLASSES.ACTIVE);
              content.style.maxHeight = '0';
            } else {
              button.classList.add(CONFIG.CLASSES.ACTIVE);
               content.style.maxHeight = content.scrollHeight + 'px';
               content.style.maxHeight = content.scrollHeight + 'px';
             }
             }
Line 1,139: Line 1,064:
     },
     },


     /**
     init: function () {
    * Initializes sidebar management
      this.initCollapsibleHeaders();
    */
      this.initCollapsibleButtons();
    init: function() {
    }
       this.initSidebarCollapsible();
  };
 
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', CollapsibleSections.init.bind(CollapsibleSections));
  } else {
    CollapsibleSections.init();
  }
})();
 
/*
Propósito da funcionalidade:
- Oferecer um sistema de recomendação de “Guardian type” baseado em opções selecionadas pelo usuário.
- Controlar accordion de seções (`.guardian-accordion`) e permitir abrir diretamente por nível de dificuldade (1–5).
- Calcular a recomendação pela contagem de votos dos `.decision-options .option.selected`, resolvendo empates escolhendo o tipo mais “difícil”.
- Atualizar o texto em `#recommendedType` e destacar visualmente o tipo recomendado com a classe `recommended`.
- Expor funções globais para uso em HTML/conteúdo do wiki:
  - `window.updateGuardianRecommendation()`
  - `window.showGuardianByDifficulty(level)`
*/
 
(function () {
  'use strict';
 
  const CONFIG = {
    SELECTORS: {
       GUARDIAN_ACCORDION: '.guardian-accordion'
    },
    CLASSES: {
      ACTIVE: 'active',
      RECOMMENDED: 'recommended'
    },
    DELAYS: {
      ACCORDION_SCROLL_DELAY: 300
     }
     }
   };
   };


   // ============================================================================
   function safeGetElementById(id) {
   // GUARDIAN DECISION HELPER
    try {
   // ============================================================================
      return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }
 
  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }
 
  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
   }
 
   function isElementInViewport(element) {
    if (!element) return false;
 
    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  }


  /**
  * Guardian Decision Helper Module
  * Manages guardian type recommendation system
  */
   const GuardianDecisionHelper = {
   const GuardianDecisionHelper = {
    /**
    * Guardian type difficulty mapping
    */
     DIFFICULTY_MAP: {
     DIFFICULTY_MAP: {
       '1': 'regular',
       '1': 'regular',
       '2': 'mighty',  
       '2': 'mighty',
       '3': 'legendary',
       '3': 'legendary',
       '4': 'superior',
       '4': 'superior',
Line 1,167: Line 1,152:
     },
     },


    /**
    * Guardian type descriptions
    */
     TYPE_DESCRIPTIONS: {
     TYPE_DESCRIPTIONS: {
       'regular': 'Perfect for beginners with limited time',
       'regular': 'Perfect for beginners with limited time',
Line 1,178: Line 1,160:
     },
     },


    /**
     initAccordion: function () {
    * Initializes accordion functionality
    */
     initAccordion: function() {
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
     
 
       accordions.forEach(accordion => {
       accordions.forEach(accordion => {
         accordion.addEventListener('click', function() {
         accordion.addEventListener('click', function () {
           this.classList.toggle(CONFIG.CLASSES.ACTIVE);
           this.classList.toggle(CONFIG.CLASSES.ACTIVE);
           const panel = this.nextElementSibling;
           const panel = this.nextElementSibling;
         
 
           if (panel) {
           if (panel) {
             if (panel.style.maxHeight) {
             if (panel.style.maxHeight) {
Line 1,200: Line 1,179:
     },
     },


    /**
     showGuardianByDifficulty: function (level) {
    * Shows guardian by difficulty level
    * @param {string} level - Difficulty level (1-5)
    */
     showGuardianByDifficulty: function(level) {
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
     
 
      // Reset all accordions
       accordions.forEach(accordion => {
       accordions.forEach(accordion => {
         accordion.classList.remove(CONFIG.CLASSES.ACTIVE);
         accordion.classList.remove(CONFIG.CLASSES.ACTIVE);
Line 1,216: Line 1,190:
       });
       });


      // Find and activate target accordion
       const guardianType = this.DIFFICULTY_MAP[level];
       const guardianType = this.DIFFICULTY_MAP[level];
       if (guardianType) {
       if (guardianType) {
Line 1,226: Line 1,199:
             panel.style.maxHeight = panel.scrollHeight + 'px';
             panel.style.maxHeight = panel.scrollHeight + 'px';
           }
           }
         
 
          // Scroll to accordion
           setTimeout(() => {
           setTimeout(() => {
             targetAccordion.scrollIntoView({  
             targetAccordion.scrollIntoView({
               behavior: 'smooth',  
               behavior: 'smooth',
               block: 'center'  
               block: 'center'
             });
             });
           }, CONFIG.DELAYS.ACCORDION_SCROLL_DELAY);
           }, CONFIG.DELAYS.ACCORDION_SCROLL_DELAY);
Line 1,238: Line 1,210:
     },
     },


    /**
     initDecisionHelper: function () {
    * Initializes decision helper functionality
    */
     initDecisionHelper: function() {
       const options = safeQuerySelectorAll('.decision-options .option');
       const options = safeQuerySelectorAll('.decision-options .option');
     
 
       options.forEach(option => {
       options.forEach(option => {
         option.addEventListener('click', function() {
         option.addEventListener('click', function () {
          // Remove selected class from siblings
           const siblings = this.parentElement.querySelectorAll('.option');
           const siblings = this.parentElement.querySelectorAll('.option');
           siblings.forEach(sibling => sibling.classList.remove('selected'));
           siblings.forEach(sibling => sibling.classList.remove('selected'));
         
 
          // Add selected class to clicked option
           this.classList.add('selected');
           this.classList.add('selected');
         
 
          // Update recommendation
           GuardianDecisionHelper.updateRecommendation();
           GuardianDecisionHelper.updateRecommendation();
         });
         });
Line 1,259: Line 1,225:
     },
     },


    /**
     updateRecommendation: function () {
    * Updates recommendation based on selected options
    */
     updateRecommendation: function() {
       const selectedOptions = safeQuerySelectorAll('.decision-options .option.selected');
       const selectedOptions = safeQuerySelectorAll('.decision-options .option.selected');
       const recommendedTypeElement = safeGetElementById('recommendedType');
       const recommendedTypeElement = safeGetElementById('recommendedType');
     
 
       if (!recommendedTypeElement) return;
       if (!recommendedTypeElement) return;


      // Show default message if no options selected
       if (selectedOptions.length === 0) {
       if (selectedOptions.length === 0) {
         recommendedTypeElement.textContent = 'Select options above';
         recommendedTypeElement.textContent = 'Select options above';
Line 1,274: Line 1,236:
       }
       }


      // Count votes for each guardian type
       const votes = {
       const votes = {
         'regular': 0,
         'regular': 0,
Line 1,283: Line 1,244:
       };
       };


      // Tally votes from selected options
       selectedOptions.forEach(option => {
       selectedOptions.forEach(option => {
         const types = option.getAttribute('data-type');
         const types = option.getAttribute('data-type');
Line 1,295: Line 1,255:
       });
       });


      // Find type with most votes
       let maxVotes = 0;
       let maxVotes = 0;
       let recommendedType = '';
       let recommendedType = '';
Line 1,310: Line 1,269:
       }
       }


      // Handle ties by choosing more challenging type
       if (tiedTypes.length > 1) {
       if (tiedTypes.length > 1) {
         const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished'];
         const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished'];
         let highestDifficultyIndex = -1;
         let highestDifficultyIndex = -1;
       
 
         tiedTypes.forEach(type => {
         tiedTypes.forEach(type => {
           const typeIndex = difficultyOrder.indexOf(type);
           const typeIndex = difficultyOrder.indexOf(type);
Line 1,324: Line 1,282:
       }
       }


      // Format recommendation text
       const formattedType = recommendedType.charAt(0).toUpperCase() + recommendedType.slice(1) + ' Guardian';
       const formattedType = recommendedType.charAt(0).toUpperCase() + recommendedType.slice(1) + ' Guardian';
       const description = this.TYPE_DESCRIPTIONS[recommendedType] || '';
       const description = this.TYPE_DESCRIPTIONS[recommendedType] || '';
       const recommendationText = `${formattedType} - ${description}`;
       const recommendationText = `${formattedType} - ${description}`;


      // Update display
       recommendedTypeElement.textContent = recommendationText;
       recommendedTypeElement.textContent = recommendationText;
       this.highlightRecommendedType(recommendedType);
       this.highlightRecommendedType(recommendedType);
     },
     },


    /**
     highlightRecommendedType: function (type) {
    * Highlights the recommended guardian type
    * @param {string} type - Guardian type to highlight
    */
     highlightRecommendedType: function(type) {
      // Remove highlight from all accordions
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
       const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
       accordions.forEach(accordion => {
       accordions.forEach(accordion => {
Line 1,345: Line 1,296:
       });
       });


      // Add highlight to recommended type
       const recommendedAccordion = safeQuerySelector(`.${type}-accordion`);
       const recommendedAccordion = safeQuerySelector(`.${type}-accordion`);
       if (recommendedAccordion) {
       if (recommendedAccordion) {
         recommendedAccordion.classList.add(CONFIG.CLASSES.RECOMMENDED);
         recommendedAccordion.classList.add(CONFIG.CLASSES.RECOMMENDED);
       
 
        // Scroll to recommended type if not visible
         if (!isElementInViewport(recommendedAccordion)) {
         if (!isElementInViewport(recommendedAccordion)) {
           recommendedAccordion.scrollIntoView({  
           recommendedAccordion.scrollIntoView({
             behavior: 'smooth',  
             behavior: 'smooth',
             block: 'center'  
             block: 'center'
           });
           });
         }
         }
Line 1,360: Line 1,309:
     },
     },


    /**
     init: function () {
    * Initializes Guardian Decision Helper
       const hasGuardianElements =
    */
     init: function() {
      // Only initialize if guardian elements exist
       const hasGuardianElements =  
         safeQuerySelector(CONFIG.SELECTORS.GUARDIAN_ACCORDION) ||
         safeQuerySelector(CONFIG.SELECTORS.GUARDIAN_ACCORDION) ||
         safeQuerySelector('.decision-options');
         safeQuerySelector('.decision-options');
     
 
       if (hasGuardianElements) {
       if (hasGuardianElements) {
         this.initAccordion();
         this.initAccordion();
         this.initDecisionHelper();
         this.initDecisionHelper();
       
 
        // Make functions globally available
         window.updateGuardianRecommendation = this.updateRecommendation.bind(this);
         window.updateGuardianRecommendation = this.updateRecommendation.bind(this);
         window.showGuardianByDifficulty = this.showGuardianByDifficulty.bind(this);
         window.showGuardianByDifficulty = this.showGuardianByDifficulty.bind(this);
Line 1,380: Line 1,324:
   };
   };


   // ============================================================================
   if (document.readyState === 'loading') {
   // USER MENU MANAGEMENT
    document.addEventListener('DOMContentLoaded', GuardianDecisionHelper.init.bind(GuardianDecisionHelper));
   // ============================================================================
   } else {
    GuardianDecisionHelper.init();
   }
})();
 
 
 
 
 


  /**
  * User Menu Management Module
  * Handles collapsible user menu functionality
  */
  const UserMenuManager = {
    /**
    * Initializes user menu collapsible functionality
    */
    initUserMenuCollapsible: function() {
      // Only for Vector Legacy skin
      if (!document.body.classList.contains('skin-vector-legacy')) {
        return;
      }


      const personalTools = safeGetElementById('p-personal');
      if (!personalTools) return;


      // Get user menu items
      const userMenuItems = [
        'pt-userpage', 'pt-mytalk', 'pt-preferences',
        'pt-watchlist', 'pt-mycontris', 'pt-logout'
      ].map(id => safeGetElementById(id)).filter(Boolean);


      if (userMenuItems.length === 0) return;


      // Create menu container
      const menuContainer = document.createElement('div');
      menuContainer.id = 'user-menu-collapsible';
      menuContainer.className = 'user-menu-container';


      // Create header/toggle button
      const menuHeader = document.createElement('div');
      menuHeader.className = 'user-menu-header';
      menuHeader.textContent = 'User';
     
      menuHeader.addEventListener('click', () => {
        menuContainer.classList.toggle(CONFIG.CLASSES.EXPANDED);
       
        const state = menuContainer.classList.contains(CONFIG.CLASSES.EXPANDED)
          ? 'expanded' : 'collapsed';
        safeSetLocalStorage(CONFIG.STORAGE_KEYS.USER_MENU_STATE, state);
      });


      // Create content container
      const menuContent = document.createElement('div');
      menuContent.className = 'user-menu-content';


      const menuList = document.createElement('ul');
      menuList.className = 'user-menu-list';


      // Move user menu items to new container
      userMenuItems.forEach(item => {
        if (item && item.parentNode) {
          menuList.appendChild(item);
        }
      });


      // Assemble menu
      menuContent.appendChild(menuList);
      menuContainer.appendChild(menuHeader);
      menuContainer.appendChild(menuContent);
      document.body.appendChild(menuContainer);


      // Restore saved state
      const savedState = safeGetLocalStorage(CONFIG.STORAGE_KEYS.USER_MENU_STATE);
      if (savedState === 'expanded') {
        menuContainer.classList.add(CONFIG.CLASSES.EXPANDED);
      }


      // Close menu on outside click
      document.addEventListener('click', (event) => {
        if (menuContainer.classList.contains(CONFIG.CLASSES.EXPANDED) &&
            !menuContainer.contains(event.target)) {
          menuContainer.classList.remove(CONFIG.CLASSES.EXPANDED);
          safeSetLocalStorage(CONFIG.STORAGE_KEYS.USER_MENU_STATE, 'collapsed');
        }
      });


      // Hover functionality
      menuContainer.addEventListener('mouseenter', () => {
        menuContainer.classList.add(CONFIG.CLASSES.EXPANDED);
      });


      menuContainer.addEventListener('mouseleave', () => {
        menuContainer.classList.remove(CONFIG.CLASSES.EXPANDED);
        safeSetLocalStorage(CONFIG.STORAGE_KEYS.USER_MENU_STATE, 'collapsed');
      });
    },


    /**
    * Initializes user menu management
    */
    init: function() {
      this.initUserMenuCollapsible();
    }
  };


  // ============================================================================
  // RESPONSIVE TABLE MANAGER
  // ============================================================================


  /**
/*
  * Responsive Table Manager Module
Propósito da funcionalidade:
  * Wraps tables in containers for viewports ≤ 550px with enhanced accessibility and performance
- Converter marcação de links no estilo MediaWiki escrita como texto (ex.: `[[Titulo]]` ou `[[Titulo|Texto]]`)
  */
   em links HTML reais (`<a href="...">`).
   const ResponsiveTableManager = {
- Ignorar contextos onde essa conversão não deve acontecer (scripts, editores, formulários, etc.).
    /**
- Preservar links `File:`/`Image:` como texto, sem conversão.
    * Table selectors to make responsive
- Gerar URLs usando `mw.util.getUrl()` quando disponível, com fallback para `index.php?title=...`.
    */
*/
    TABLE_SELECTORS: [
      'table.wikitable',
      'table.mw-datatable',
      'table.faq-table',
      'table.daily-table'
    ],


    /**
(function () {
    * Viewport breakpoint for responsive behavior
  'use strict';
    */
    RESPONSIVE_BREAKPOINT: 550,


     /**
  function isInExcludedContext(node) {
    * Checks if viewport width is ≤ 550px
     if (!node || !node.parentElement) return false;
    * @returns {boolean} True if viewport requires responsive table behavior
 
    */
    const excludedSelectors = [
     isResponsiveViewport: function() {
      'script', 'style', 'textarea', 'input', 'select', 'option',
      return window.innerWidth <= this.RESPONSIVE_BREAKPOINT;
      '.ve-ui-surface', '.mw-editform', '.CodeMirror', '.cm-editor', '.ace_editor'
    },
     ].join(', ');
 
    return !!node.parentElement.closest(excludedSelectors);
  }


    /**
  const WikiLinkProcessor = {
    * Wraps a table in a container for responsive viewports
     processWikiLinks: function () {
    * @param {HTMLElement} table - Table element to wrap
       if (!document.body || document.body.textContent.indexOf('[[') === -1) {
    */
     wrapTable: function(table) {
      // Skip if already wrapped or not on responsive viewport
       if (table.closest('.table-container') || !this.isResponsiveViewport()) {
         return;
         return;
       }
       }


      // Create wrapper with enhanced accessibility
       const walker = document.createTreeWalker(
       const wrapper = document.createElement('div');
        document.body,
      wrapper.className = 'table-container';
        NodeFilter.SHOW_TEXT,
      wrapper.setAttribute('role', 'region');
        null,
      wrapper.setAttribute('aria-label', 'Tabela com rolagem horizontal');
        false
      wrapper.setAttribute('tabindex', '0');
       );
     
 
       // Insert wrapper before table
       const textNodes = [];
      table.parentNode.insertBefore(wrapper, table);
       let node;
     
       // Move table into wrapper
      wrapper.appendChild(table);
        
      // Add keyboard navigation support
      this.addKeyboardNavigation(wrapper);
    },


    /**
      while ((node = walker.nextNode())) {
    * Unwraps tables when viewport becomes larger
        const value = node.nodeValue;
    * @param {HTMLElement} table - Table element to unwrap
        if (!value || value.indexOf('[[') === -1 || isInExcludedContext(node)) {
    */
          continue;
    unwrapTable: function(table) {
         }
      const wrapper = table.closest('.table-container');
         textNodes.push(node);
      if (wrapper && !this.isResponsiveViewport()) {
        // Move table out of wrapper
        wrapper.parentNode.insertBefore(table, wrapper);
         // Remove wrapper
         wrapper.remove();
       }
       }
    },


    /**
      const linkRegex = /\[\[([^\|\]]+)(?:\|([^\]]+))?\]\]/g;
    * Adds keyboard navigation support to table containers
 
    * @param {HTMLElement} wrapper - Table wrapper element
      textNodes.forEach(textNode => {
    */
        const sourceText = textNode.nodeValue;
    addKeyboardNavigation: function(wrapper) {
        if (!linkRegex.test(sourceText)) return;
      wrapper.addEventListener('keydown', (event) => {
 
         const { key } = event;
        linkRegex.lastIndex = 0;
         const scrollAmount = 100;
 
          
         const parent = textNode.parentNode;
         switch (key) {
         const fragment = document.createDocumentFragment();
           case 'ArrowLeft':
         let lastIndex = 0;
             event.preventDefault();
         let match;
             wrapper.scrollLeft -= scrollAmount;
 
            break;
        while ((match = linkRegex.exec(sourceText)) !== null) {
           case 'ArrowRight':
           if (match.index > lastIndex) {
            event.preventDefault();
             fragment.appendChild(
            wrapper.scrollLeft += scrollAmount;
              document.createTextNode(sourceText.slice(lastIndex, match.index))
             break;
             );
          case 'Home':
          }
             event.preventDefault();
 
             wrapper.scrollLeft = 0;
           const rawTitle = (match[1] || '').trim();
             break;
          const displayText = (match[2] != null ? match[2] : rawTitle).trim();
          case 'End':
 
             event.preventDefault();
          if (/^(File:|Image:)/i.test(rawTitle)) {
            wrapper.scrollLeft = wrapper.scrollWidth;
             // Handle File/Image links
            break;
            const cleanFilename = rawTitle.replace(/^(File:|Image:)/i, '').trim();
        }
             const parts = displayText.split('|');
      });
             let caption = '';
    },
             let isThumb = false;
           
            // Basic parser for options (thumb, caption)
            // Assumes the last part is the caption if it's not a keyword
            // This is a simplified parser compared to full MediaWiki
             parts.forEach((part, index) => {
              const p = part.trim().toLowerCase();
              if (p === 'thumb' || p === 'thumbnail') {
                isThumb = true;
              } else if (['left', 'right', 'center', 'none', 'frame', 'frameless', 'border'].includes(p)) {
                // Alignment/Frame options - currently ignored or could be added as classes
              } else if (p.match(/^\d+px$/)) {
                // Size option - currently ignored
              } else {
                // Assume it's caption (usually the last one, but we take the last non-keyword)
                caption = part.trim();
              }
            });


    /**
            // Use Special:FilePath to get the image source
    * Processes all tables based on viewport size with performance optimization
            let src;
    */
            if (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function') {
    processAllTables: function() {
               src = window.mw.util.getUrl('Special:FilePath/' + cleanFilename);
      // Use requestAnimationFrame for better performance
      requestAnimationFrame(() => {
        this.TABLE_SELECTORS.forEach(selector => {
          const tables = document.querySelectorAll(selector);
          tables.forEach(table => {
            if (this.isResponsiveViewport()) {
               this.wrapTable(table);
             } else {
             } else {
               this.unwrapTable(table);
               // Fallback for local development or external usage
              // Use absolute URL to avoid local file:// or relative path issues which cause timeouts/delays
              src = 'https://mewsie.world/CoraTOWiki/index.php?title=Special:FilePath/' + encodeURIComponent(cleanFilename);
             }
             }
          });
        });
      });
    },


    /**
            if (isThumb) {
    * Handles window resize events with debouncing
              // Create structure: <div class="image-container"><img ...><div class="image-caption">...</div></div>
    */
              const container = document.createElement('div');
    handleResize: function() {
              container.className = 'image-container';
      // Clear existing timeout
 
      if (this.resizeTimeout) {
              const img = document.createElement('img');
        clearTimeout(this.resizeTimeout);
              img.setAttribute('src', src);
      }
              img.setAttribute('alt', caption || cleanFilename);
     
              img.setAttribute('loading', 'lazy'); // Optimize loading
      // Debounce resize handling for better performance
              img.setAttribute('decoding', 'async'); // Optimize decoding
      this.resizeTimeout = setTimeout(() => {
              container.appendChild(img);
        this.processAllTables();
 
      }, 100);
              if (caption) {
    },
                const capDiv = document.createElement('div');
                capDiv.className = 'image-caption';
                capDiv.textContent = caption;
                container.appendChild(capDiv);
              }


    /**
              fragment.appendChild(container);
    * Sets up mutation observer to handle dynamically added tables with performance optimization
            } else {
    */
              // Inline image or simple image
    setupMutationObserver: function() {
              const img = document.createElement('img');
      const observer = new MutationObserver((mutations) => {
              img.setAttribute('src', src);
        // Batch process mutations for better performance
              img.setAttribute('alt', caption || cleanFilename);
        const tablesToProcess = [];
              img.setAttribute('loading', 'lazy'); // Optimize loading
       
               img.setAttribute('decoding', 'async'); // Optimize decoding
        mutations.forEach((mutation) => {
               fragment.appendChild(img);
          mutation.addedNodes.forEach((node) => {
            if (node.nodeType === Node.ELEMENT_NODE) {
              // Check if the added node is a table or contains tables
               const tables = node.matches && this.TABLE_SELECTORS.some(selector => node.matches(selector))
                ? [node]
                : node.querySelectorAll ? node.querySelectorAll(this.TABLE_SELECTORS.join(', ')) : [];
                
              tablesToProcess.push(...tables);
             }
             }
           });
           } else {
        });
            const cleanTitle = rawTitle.charAt(0) === ':' ? rawTitle.slice(1) : rawTitle;
       
 
        // Process all found tables in a single animation frame
            const href = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
        if (tablesToProcess.length > 0) {
              ? window.mw.util.getUrl(cleanTitle)
          requestAnimationFrame(() => {
              : ('index.php?title=' + encodeURIComponent(cleanTitle));
             tablesToProcess.forEach(table => {
 
              if (this.isResponsiveViewport()) {
            const anchor = document.createElement('a');
                this.wrapTable(table);
            anchor.className = 'mw-link-internal';
              }
             anchor.setAttribute('href', href);
             });
            anchor.appendChild(document.createTextNode(displayText));
           });
            fragment.appendChild(anchor);
          }
 
          lastIndex = linkRegex.lastIndex;
        }
 
        if (lastIndex < sourceText.length) {
          fragment.appendChild(
             document.createTextNode(sourceText.slice(lastIndex))
           );
         }
         }
      });


      observer.observe(document.body, {
        parent.insertBefore(fragment, textNode);
         childList: true,
         parent.removeChild(textNode);
        subtree: true
       });
       });
     
      // Store observer reference for cleanup if needed
      this.mutationObserver = observer;
     },
     },


    /**
     init: function () {
    * Initializes the responsive table system with enhanced MediaWiki compatibility
       if (document.readyState === 'loading') {
    */
         document.addEventListener('DOMContentLoaded', this.processWikiLinks);
     init: function() {
      // Wait for MediaWiki to be ready
       if (typeof mw !== 'undefined' && mw.loader) {
         mw.loader.using(['mediawiki.util'], () => {
          this.initializeSystem();
        });
       } else {
       } else {
        // Fallback for non-MediaWiki environments
         this.processWikiLinks();
         this.initializeSystem();
       }
       }
    },
   
    /**
    * Core initialization logic
    */
    initializeSystem: function() {
      // Process existing tables
      this.processAllTables();
     
      // Set up resize handler with passive listener for better performance
      window.addEventListener('resize', this.handleResize.bind(this), { passive: true });
     
      // Set up mutation observer for dynamic content
      this.setupMutationObserver();
     
      // Add CSS class to body to indicate responsive tables are active
      document.body.classList.add('responsive-tables-enabled');
     
      console.log('Responsive Table Manager initialized');
     }
     }
   };
   };


   const ImageLightbox = {
   WikiLinkProcessor.init();
    overlay: null,
    bodyOverflow: '',
    init: function() {
      document.addEventListener('click', this.handleDocumentClick.bind(this), false);
    },
    buildOverlay: function() {
      const overlay = document.createElement('div');
      overlay.className = 'image-lightbox-overlay';
      const theme = document.documentElement.getAttribute('data-theme') || 'light';
      const bg = theme === 'dark' ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.7)';
      overlay.style.position = 'fixed';
      overlay.style.top = '0';
      overlay.style.right = '0';
      overlay.style.bottom = '0';
      overlay.style.left = '0';
      overlay.style.display = 'flex';
      overlay.style.alignItems = 'center';
      overlay.style.justifyContent = 'center';
      overlay.style.background = bg;
      overlay.style.zIndex = '9999';
      overlay.style.padding = '2vw';
      overlay.style.cursor = 'zoom-out';
      overlay.setAttribute('role', 'dialog');
      overlay.setAttribute('aria-modal', 'true');
      overlay.addEventListener('click', (e) => { if (e.target === overlay) this.close(); });
      document.addEventListener('keydown', this.handleKeydown);
      return overlay;
    },
    handleKeydown: function(e) {
      if (e.key === 'Escape') {
        const self = ImageLightbox;
        if (self.overlay) { self.close(); }
      }
    },
    findImageFromTarget: function(target) {
      const el = target.closest('img, .image-container, .image-grid img');
      if (!el) return null;
      if (el.tagName && el.tagName.toLowerCase() === 'img') return el;
      const img = el.querySelector('img');
      return img || null;
    },
    handleDocumentClick: function(e) {
      if (this.overlay && this.overlay.contains(e.target)) return;
      const img = this.findImageFromTarget(e.target);
      if (!img) return;
      e.preventDefault();
      e.stopPropagation();
      this.open(img);
    },
    open: function(img) {
      if (this.overlay) return;
      this.overlay = this.buildOverlay();
      this.bodyOverflow = document.body.style.overflow || '';
      document.body.style.overflow = 'hidden';
      const content = document.createElement('div');
      content.style.position = 'relative';
      content.style.maxWidth = '90vw';
      content.style.maxHeight = '90vh';
      const clone = document.createElement('img');
      clone.src = img.currentSrc || img.src;
      clone.alt = img.alt || '';
      clone.style.maxWidth = '90vw';
      clone.style.maxHeight = '90vh';
      clone.style.borderRadius = '12px';
      clone.style.boxShadow = '0 10px 30px rgba(0,0,0,0.25)';
      clone.style.transform = 'scale(0.98)';
      clone.style.opacity = '0';
      clone.style.transition = 'transform 150ms ease, opacity 150ms ease';
      clone.style.willChange = 'transform, opacity';
      content.appendChild(clone);
      const captionText = this.getCaptionText(img);
      if (captionText) {
        const caption = document.createElement('div');
        caption.textContent = captionText;
        caption.style.marginTop = '0.75rem';
        caption.style.fontSize = '0.9rem';
        caption.style.textAlign = 'center';
        const theme = document.documentElement.getAttribute('data-theme') || 'light';
        caption.style.color = theme === 'dark' ? '#a8c6e8' : '#666';
        content.appendChild(caption);
      }
      this.overlay.appendChild(content);
      document.body.appendChild(this.overlay);
      requestAnimationFrame(() => {
        clone.style.transform = 'scale(1.25)';
        clone.style.opacity = '1';
      });
    },
    getCaptionText: function(img) {
      const container = img.closest('.image-container');
      const capEl = container ? container.querySelector('.image-caption') : null;
      if (capEl && capEl.textContent) {
        return capEl.textContent.trim();
      }
      return img.alt ? img.alt.trim() : '';
    },
    close: function() {
      if (!this.overlay) return;
      document.removeEventListener('keydown', this.handleKeydown);
      if (this.overlay.parentNode) this.overlay.parentNode.removeChild(this.overlay);
      this.overlay = null;
      document.body.style.overflow = this.bodyOverflow;
    }
  };
 
  // ============================================================================
  // MAIN INITIALIZATION
  // ============================================================================
 
  /**
  * Main initialization function
  * Initializes all modules when DOM is ready
  */
  function initializeAll() {
    try {
      ThemeManager.init();
      // Initialize core MediaWiki modules
      TabNavigation.init();
      NavigationHandlers.init();
      WikiLinkProcessor.init();
      MobileInterface.init();
      CollapsibleSections.init();
      SidebarManager.init();
      GuardianDecisionHelper.init();
      UserMenuManager.init();
      ResponsiveTableManager.init();
     
      // Initialize Quest Tracker module
      QuestTracker.init();
     
      // Initialize Back to Top module
      BackToTop.init();
      ImageLightbox.init();
     
      console.log('MediaWiki Common JavaScript with Quest Tracker and Back to Top initialized successfully');
    } catch (error) {
      console.error('Error initializing MediaWiki Common JavaScript:', error);
    }
  }
 
  // Initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeAll);
  } else {
    initializeAll();
  }
 
  // Make Quest Tracker functions globally available for backward compatibility
  window.initQuestTracker = QuestTracker.init.bind(QuestTracker);
  window.loadQuestProgress = QuestTracker.loadQuestProgress.bind(QuestTracker);
  window.saveQuestProgress = QuestTracker.saveQuestProgress.bind(QuestTracker);
  window.updateProgressDisplay = QuestTracker.updateProgressDisplay.bind(QuestTracker);
  window.resetAllProgress = QuestTracker.resetAllProgress.bind(QuestTracker);
  window.__testThemeToggle = ThemeManager.selfTest.bind(ThemeManager);
 
})();
})();

Latest revision as of 03:14, 29 January 2026

/*
Propósito da funcionalidade:
- Criar e controlar um botão “voltar ao topo” que aparece após rolar a página.
- Exibir/ocultar com classes (`show` e `pulse`) e realizar rolagem suave até o topo ao clicar.
- Inserir automaticamente o botão no `document.body` e registrar listeners de `scroll` (passivo) e `click`.
*/

(function () {
  'use strict';

  const CONFIG = {
    CLASSES: {
      SHOW: 'show',
      PULSE: 'pulse'
    }
  };

  const BackToTop = {
    button: null,
    isVisible: false,
    scrollThreshold: 300,

    createButton: function () {
      const button = document.createElement('button');
      button.className = 'btt-button';
      button.setAttribute('aria-label', 'Voltar ao topo');
      button.setAttribute('title', 'Voltar ao topo');
      document.body.appendChild(button);
      return button;
    },

    showButton: function () {
      if (!this.isVisible && this.button) {
        this.button.classList.add(CONFIG.CLASSES.SHOW);
        this.button.classList.add(CONFIG.CLASSES.PULSE);
        this.isVisible = true;

        setTimeout(() => {
          if (this.button) {
            this.button.classList.remove(CONFIG.CLASSES.PULSE);
          }
        }, 6000);
      }
    },

    hideButton: function () {
      if (this.isVisible && this.button) {
        this.button.classList.remove(CONFIG.CLASSES.SHOW);
        this.button.classList.remove(CONFIG.CLASSES.PULSE);
        this.isVisible = false;
      }
    },

    handleScroll: function () {
      const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

      if (scrollTop > this.scrollThreshold) {
        this.showButton();
      } else {
        this.hideButton();
      }
    },

    handleClick: function () {
      window.scrollTo({
        top: 0,
        behavior: 'smooth'
      });
    },

    init: function () {
      this.button = this.createButton();

      const boundHandleScroll = this.handleScroll.bind(this);
      const boundHandleClick = this.handleClick.bind(this);

      window.addEventListener('scroll', boundHandleScroll, { passive: true });
      this.button.addEventListener('click', boundHandleClick);

      this.handleScroll();

      console.log('Back to Top button initialized');
    }
  };

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

/*
Propósito da funcionalidade:
- Permitir navegação por clique em “cards” que tenham atributo `data-link`.
- Suportar três tipos de destino:
  - Âncoras internas (ex.: `#secao`) com rolagem suave.
  - URLs externas absolutas (`http://` ou `https://`).
  - Títulos de páginas MediaWiki, resolvidos via `mw.util.getUrl()` quando disponível.
*/

(function () {
  'use strict';

  const CONFIG = {
    SELECTORS: {
      CARD_LINKS: '.card[data-link], .destaque-card[data-link]'
    }
  };

  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
  }

  const NavigationHandlers = {
    handleCardClick: function (event) {
      const card = event.target.closest(CONFIG.SELECTORS.CARD_LINKS);
      if (!card) return;

      const link = card.getAttribute('data-link');
      if (!link) return;

      if (link.charAt(0) === '#') {
        event.preventDefault();
        const anchorElement = safeQuerySelector(link);
        if (anchorElement) {
          anchorElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
        return;
      }

      if (/^https?:\/\//i.test(link)) {
        window.location.href = link;
        return;
      }

      const targetUrl = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
        ? window.mw.util.getUrl(link)
        : ('index.php?title=' + encodeURIComponent(link));

      window.location.href = targetUrl;
    },

    init: function () {
      document.addEventListener('click', this.handleCardClick, false);
    }
  };

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

/*
Propósito da funcionalidade:
- Criar um menu “hamburger” para abrir/fechar a sidebar no skin `vector-legacy`.
- Alternar classes (`mobile-open`, `sidebar-open`, `active`) para controlar layout em mobile.
- Fechar a sidebar ao clicar fora do painel ou ao pressionar `Escape`.
*/

(function () {
  'use strict';

  const CONFIG = {
    CLASSES: {
      ACTIVE: 'active',
      MOBILE_OPEN: 'mobile-open',
      SIDEBAR_OPEN: 'sidebar-open'
    }
  };

  function safeGetElementById(id) {
    try {
      return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }

  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
  }

  const MobileInterface = {
    initialized: false,

    getPanel: function () {
      return safeGetElementById('mw-panel');
    },

    isApplicable: function () {
      return document.body.classList.contains('skin-vector-legacy') && !!this.getPanel();
    },

    createHamburgerMenu: function () {
      if (this.initialized) return;
      if (!this.isApplicable()) {
        return;
      }

      if (document.querySelector('.mobile-hamburger-menu')) {
        this.initialized = true;
        return;
      }

      const panel = this.getPanel();
      if (!panel) return;

      const hamburger = document.createElement('button');
      hamburger.className = 'mobile-hamburger-menu';
      hamburger.setAttribute('aria-label', 'Toggle navigation menu');
      hamburger.setAttribute('type', 'button');
      hamburger.innerHTML = '<span></span><span></span><span></span>';

      const content = safeGetElementById('content') || safeQuerySelector('.mw-body');
      if (content && content.parentNode) {
        content.parentNode.insertBefore(hamburger, content);
      }

      const toggleSidebar = () => {
        panel.classList.toggle(CONFIG.CLASSES.MOBILE_OPEN);
        hamburger.classList.toggle(CONFIG.CLASSES.ACTIVE);
        document.body.classList.toggle(CONFIG.CLASSES.SIDEBAR_OPEN);
        try {
          const open = panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN);
          document.dispatchEvent(new CustomEvent('cora:mobile-sidebar-toggle', { detail: { open } }));
        } catch (e) {
          document.dispatchEvent(new Event('cora:mobile-sidebar-toggle'));
        }
      };

      hamburger.addEventListener('click', (event) => {
        event.preventDefault();
        event.stopPropagation();
        toggleSidebar();
      });

      document.addEventListener('click', (event) => {
        if (panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN) &&
          !panel.contains(event.target) &&
          !hamburger.contains(event.target)) {
          panel.classList.remove(CONFIG.CLASSES.MOBILE_OPEN);
          hamburger.classList.remove(CONFIG.CLASSES.ACTIVE);
          document.body.classList.remove(CONFIG.CLASSES.SIDEBAR_OPEN);
        }
      });

      document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape' && panel.classList.contains(CONFIG.CLASSES.MOBILE_OPEN)) {
          panel.classList.remove(CONFIG.CLASSES.MOBILE_OPEN);
          hamburger.classList.remove(CONFIG.CLASSES.ACTIVE);
          document.body.classList.remove(CONFIG.CLASSES.SIDEBAR_OPEN);
        }
      });

      this.initialized = true;
    },

    init: function () {
      this.createHamburgerMenu();
    }
  };

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', MobileInterface.init.bind(MobileInterface));
  } else {
    MobileInterface.init();
  }
})();
/*
Propósito da funcionalidade:
- Tornar os portais/menus laterais do skin `vector-legacy` colapsáveis.
- Colapsar automaticamente todas as seções do painel (exceto a primeira) ao iniciar.
- Persistir estado expandido/colapsado por seção via `localStorage` usando prefixo `sidebar-<id>`.
- Ajustar alturas (`maxHeight`) ao expandir/colapsar e recalcular em `resize`.
*/

(function () {
  'use strict';

  const CONFIG = {
    CLASSES: {
      COLLAPSED: 'collapsed'
    },
    STORAGE_KEYS: {
      SIDEBAR_PREFIX: 'sidebar-'
    }
  };

  function ensureSidebarStyles() {
    if (document.getElementById('cora-sidebar-collapsible-styles')) return;
    const style = document.createElement('style');
    style.id = 'cora-sidebar-collapsible-styles';
    style.textContent =
      'body.skin-vector-legacy #mw-panel .vector-menu-content,body.skin-vector-legacy #mw-panel .portal .body{overflow:hidden;transition:max-height .4s ease,opacity .3s ease;opacity:1}' +
      'body.skin-vector-legacy #mw-panel .vector-menu-portal.collapsed .vector-menu-content,body.skin-vector-legacy #mw-panel .portal.collapsed .body{max-height:0!important;opacity:0;padding:0}' +
      'body.skin-vector-legacy #mw-panel .vector-menu-portal .vector-menu-heading,body.skin-vector-legacy #mw-panel .portal h3{cursor:pointer;user-select:none}';
    document.head.appendChild(style);
  }

  function safeGetElementById(id) {
    try {
      return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }

  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }

  function safeGetLocalStorage(key, defaultValue = '') {
    try {
      return localStorage.getItem(key) || defaultValue;
    } catch (error) {
      console.warn(`localStorage access failed for key '${key}':`, error);
      return defaultValue;
    }
  }

  function safeSetLocalStorage(key, value) {
    try {
      localStorage.setItem(key, value);
      return true;
    } catch (error) {
      console.warn(`localStorage write failed for key '${key}':`, error);
      return false;
    }
  }

  const SidebarManager = {
    portals: null,
    getPortalId: function (portal) {
      const heading = portal.querySelector('.vector-menu-heading, h3');
      if (heading) {
        return heading.textContent.trim().toLowerCase().replace(/\s+/g, '-');
      }
      return 'unknown-portal';
    },

    syncExpandedHeights: function () {
      if (!this.portals) return;
      this.portals.forEach(portal => {
        if (portal.classList.contains(CONFIG.CLASSES.COLLAPSED)) return;
        const content = portal.querySelector('.vector-menu-content, .body');
        if (!content) return;
        content.style.maxHeight = content.scrollHeight + 'px';
        content.style.opacity = '1';
      });
    },

    initSidebarCollapsible: function () {
      if (!document.body.classList.contains('skin-vector-legacy')) {
        return;
      }

      const panel = safeGetElementById('mw-panel');
      if (!panel) return;

      ensureSidebarStyles();

      const portals = safeQuerySelectorAll('.vector-menu-portal, .portal', panel);
      this.portals = Array.from(portals);

      portals.forEach((portal, index) => {
        if (index > 0) {
          portal.classList.add(CONFIG.CLASSES.COLLAPSED);
          const content = portal.querySelector('.vector-menu-content, .body');
          if (content) {
            content.style.maxHeight = '0';
            content.style.opacity = '0';
          }
        }
      });

      const headings = safeQuerySelectorAll('.vector-menu-heading, .portal h3', panel);

      headings.forEach(heading => {
        heading.addEventListener('click', (event) => {
          event.preventDefault();
          event.stopPropagation();

          const portal = heading.closest('.vector-menu-portal, .portal');
          if (!portal) return;

          const content = portal.querySelector('.vector-menu-content, .body');
          if (!content) return;

          const isCollapsed = portal.classList.contains(CONFIG.CLASSES.COLLAPSED);
          const portalId = this.getPortalId(portal);

          if (isCollapsed) {
            portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
            content.style.maxHeight = content.scrollHeight + 'px';
            content.style.opacity = '1';
            safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'expanded');
          } else {
            portal.classList.add(CONFIG.CLASSES.COLLAPSED);
            content.style.maxHeight = '0';
            content.style.opacity = '0';
            safeSetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + portalId, 'collapsed');
          }
        });
      });

      portals.forEach((portal, index) => {
        if (index === 0) return;

        const savedState = safeGetLocalStorage(CONFIG.STORAGE_KEYS.SIDEBAR_PREFIX + this.getPortalId(portal));
        const content = portal.querySelector('.vector-menu-content, .body');

        if (savedState === 'expanded' && content) {
          portal.classList.remove(CONFIG.CLASSES.COLLAPSED);
          content.style.maxHeight = content.scrollHeight + 'px';
          content.style.opacity = '1';
        }
      });

      window.addEventListener('resize', () => {
        this.syncExpandedHeights();
      });

      document.addEventListener('cora:mobile-sidebar-toggle', () => {
        this.syncExpandedHeights();
      });
    },

    init: function () {
      this.initSidebarCollapsible();
    }
  };

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

/*
Propósito da funcionalidade:
- Abrir imagens em um “lightbox” (overlay) ao clicar, permitindo ampliar e visualizar com foco.
- Fechar ao clicar no fundo do overlay ou ao pressionar `Escape`.
- Bloquear rolagem do body enquanto o overlay estiver aberto e restaurar ao fechar.
- Exibir legenda baseada em `.image-container .image-caption` ou no `alt` da imagem.
- Adaptar a cor do fundo e da legenda ao tema atual via atributo `data-theme` no `<html>`.
*/

(function () {
  'use strict';

  const ImageLightbox = {
    overlay: null,
    bodyOverflow: '',
    init: function () {
      document.addEventListener('click', this.handleDocumentClick.bind(this), false);
    },
    shouldIgnoreClick: function (target) {
      if (!target || !target.closest) return false;
      if (target.closest('a[href]')) return true;
      if (target.closest('.nav-tabs, .nav-tab, .nav-tab-fgod, .nested-tabs, .nested-tab')) return true;
      return false;
    },
    buildOverlay: function () {
      const overlay = document.createElement('div');
      overlay.className = 'image-lightbox-overlay';
      const theme = document.documentElement.getAttribute('data-theme') || 'light';
      const bg = theme === 'dark' ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.7)';
      overlay.style.position = 'fixed';
      overlay.style.top = '0';
      overlay.style.right = '0';
      overlay.style.bottom = '0';
      overlay.style.left = '0';
      overlay.style.display = 'flex';
      overlay.style.alignItems = 'center';
      overlay.style.justifyContent = 'center';
      overlay.style.background = bg;
      overlay.style.zIndex = '9999';
      overlay.style.padding = '2vw';
      overlay.style.cursor = 'zoom-out';
      overlay.setAttribute('role', 'dialog');
      overlay.setAttribute('aria-modal', 'true');
      overlay.addEventListener('click', (e) => { if (e.target === overlay) this.close(); });
      document.addEventListener('keydown', this.handleKeydown);
      return overlay;
    },
    handleKeydown: function (e) {
      if (e.key === 'Escape') {
        const self = ImageLightbox;
        if (self.overlay) { self.close(); }
      }
    },
    findImageFromTarget: function (target) {
      const el = target.closest('img, .image-container, .image-grid img');
      if (!el) return null;
      if (el.tagName && el.tagName.toLowerCase() === 'img') return el;
      const img = el.querySelector('img');
      return img || null;
    },
    handleDocumentClick: function (e) {
      if (this.overlay && this.overlay.contains(e.target)) return;
      if (this.shouldIgnoreClick(e.target)) return;
      const img = this.findImageFromTarget(e.target);
      if (!img) return;
      e.preventDefault();
      e.stopPropagation();
      this.open(img);
    },
    open: function (img) {
      if (this.overlay) return;
      this.overlay = this.buildOverlay();
      this.bodyOverflow = document.body.style.overflow || '';
      document.body.style.overflow = 'hidden';
      const content = document.createElement('div');
      content.style.position = 'relative';
      content.style.maxWidth = '90vw';
      content.style.maxHeight = '90vh';
      const clone = document.createElement('img');
      clone.src = img.currentSrc || img.src;
      clone.alt = img.alt || '';
      clone.style.maxWidth = '90vw';
      clone.style.maxHeight = '90vh';
      clone.style.borderRadius = '12px';
      clone.style.boxShadow = '0 10px 30px rgba(0,0,0,0.25)';
      clone.style.transform = 'scale(0.98)';
      clone.style.opacity = '0';
      clone.style.transition = 'transform 150ms ease, opacity 150ms ease';
      clone.style.willChange = 'transform, opacity';
      content.appendChild(clone);
      const captionText = this.getCaptionText(img);
      if (captionText) {
        const caption = document.createElement('div');
        caption.textContent = captionText;
        caption.style.marginTop = '0.75rem';
        caption.style.fontSize = '0.9rem';
        caption.style.textAlign = 'center';
        const theme = document.documentElement.getAttribute('data-theme') || 'light';
        caption.style.color = theme === 'dark' ? '#a8c6e8' : '#666';
        content.appendChild(caption);
      }
      this.overlay.appendChild(content);
      document.body.appendChild(this.overlay);
      requestAnimationFrame(() => {
        clone.style.transform = 'scale(1.25)';
        clone.style.opacity = '1';
      });
    },
    getCaptionText: function (img) {
      const container = img.closest('.image-container');
      const capEl = container ? container.querySelector('.image-caption') : null;
      if (capEl && capEl.textContent) {
        return capEl.textContent.trim();
      }
      return img.alt ? img.alt.trim() : '';
    },
    close: function () {
      if (!this.overlay) return;
      document.removeEventListener('keydown', this.handleKeydown);
      if (this.overlay.parentNode) this.overlay.parentNode.removeChild(this.overlay);
      this.overlay = null;
      document.body.style.overflow = this.bodyOverflow;
    }
  };

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

( function () {
	'use strict';

	const CONFIG = {
		CLASSES: {
			EXPANDED: 'expanded'
		},
		STORAGE_KEYS: {
			USER_MENU_STATE: 'user-menu-state',
			PERSONAL_MENU_STATE: 'personal-menu-state'
		},
		BODY_CLASSES: {
			HAS_USER_MENU: 'has-user-menu-collapsible',
			HAS_PERSONAL_MENU: 'has-personal-menu-collapsible'
		}
	};

	/**
	 * @typedef {Object} CollapsibleMenuOptions
	 * @property {string} containerId
	 * @property {string} containerClass
	 * @property {string} headerClass
	 * @property {string} contentClass
	 * @property {string} listClass
	 * @property {string} headerText
	 * @property {string} stateStorageKey
	 * @property {string} bodyClass
	 * @property {string[]} itemIds
	 */

	/**
	 * @param {string} id
	 * @return {HTMLElement|null}
	 */
	function safeGetElementById( id ) {
		try {
			return document.getElementById( id );
		} catch ( error ) {
			return null;
		}
	}

	/**
	 * @param {string} key
	 * @param {string} [defaultValue]
	 * @return {string}
	 */
	function safeGetLocalStorage( key, defaultValue = '' ) {
		try {
			return localStorage.getItem( key ) || defaultValue;
		} catch ( error ) {
			return defaultValue;
		}
	}

	/**
	 * @param {string} key
	 * @param {string} value
	 * @return {boolean}
	 */
	function safeSetLocalStorage( key, value ) {
		try {
			localStorage.setItem( key, value );
			return true;
		} catch ( error ) {
			return false;
		}
	}

	/**
	 * @param {CollapsibleMenuOptions} options
	 * @return {HTMLElement|null}
	 */
	function buildCollapsibleMenu( options ) {
		const existing = safeGetElementById( options.containerId );
		if ( existing ) {
			return existing;
		}

		const items = options.itemIds
			.map( ( id ) => safeGetElementById( id ) )
			.filter( Boolean );
		if ( items.length === 0 ) {
			return null;
		}

		const menuContainer = document.createElement( 'div' );
		menuContainer.id = options.containerId;
		menuContainer.className = options.containerClass;

		const menuHeader = document.createElement( 'div' );
		menuHeader.className = options.headerClass;
		menuHeader.textContent = options.headerText;

		menuHeader.addEventListener( 'click', () => {
			menuContainer.classList.toggle( CONFIG.CLASSES.EXPANDED );

			const state = menuContainer.classList.contains( CONFIG.CLASSES.EXPANDED ) ?
				'expanded' :
				'collapsed';
			safeSetLocalStorage( options.stateStorageKey, state );
		} );

		const menuContent = document.createElement( 'div' );
		menuContent.className = options.contentClass;

		const menuList = document.createElement( 'ul' );
		menuList.className = options.listClass;

		items.forEach( ( item ) => {
			if ( item && item.parentNode ) {
				menuList.appendChild( item );
			}
		} );

		menuContent.appendChild( menuList );
		menuContainer.appendChild( menuHeader );
		menuContainer.appendChild( menuContent );
		document.body.appendChild( menuContainer );

		document.body.classList.add( options.bodyClass );

		const savedState = safeGetLocalStorage( options.stateStorageKey );
		if ( savedState === 'expanded' ) {
			menuContainer.classList.add( CONFIG.CLASSES.EXPANDED );
		}

		document.addEventListener( 'click', ( event ) => {
			const target = event.target;
			if (
				menuContainer.classList.contains( CONFIG.CLASSES.EXPANDED ) &&
				target instanceof Node &&
				!menuContainer.contains( target )
			) {
				menuContainer.classList.remove( CONFIG.CLASSES.EXPANDED );
				safeSetLocalStorage( options.stateStorageKey, 'collapsed' );
			}
		} );

		menuContainer.addEventListener( 'mouseenter', () => {
			menuContainer.classList.add( CONFIG.CLASSES.EXPANDED );
		} );

		menuContainer.addEventListener( 'mouseleave', () => {
			menuContainer.classList.remove( CONFIG.CLASSES.EXPANDED );
			safeSetLocalStorage( options.stateStorageKey, 'collapsed' );
		} );

		return menuContainer;
	}

	const UserMenuManager = {
		initialized: false,

		initUserMenuCollapsible: function () {
			if ( this.initialized ) {
				return;
			}

			if ( !document.body.classList.contains( 'skin-vector-legacy' ) ) {
				return;
			}

			const personalTools = safeGetElementById( 'p-personal' );
			if ( !personalTools ) {
				return;
			}

			const isLoggedIn = !!safeGetElementById( 'pt-userpage' );
			const isAnonymous = !isLoggedIn && !!safeGetElementById( 'pt-login' );

			if ( isLoggedIn ) {
				buildCollapsibleMenu( {
					containerId: 'user-menu-collapsible',
					containerClass: 'user-menu-container',
					headerClass: 'user-menu-header',
					contentClass: 'user-menu-content',
					listClass: 'user-menu-list',
					headerText: 'User',
					stateStorageKey: CONFIG.STORAGE_KEYS.USER_MENU_STATE,
					bodyClass: CONFIG.BODY_CLASSES.HAS_USER_MENU,
					itemIds: [
						'pt-userpage', 'pt-mytalk', 'pt-preferences',
						'pt-watchlist', 'pt-mycontris', 'pt-logout'
					]
				} );
			}

			if ( isAnonymous ) {
				buildCollapsibleMenu( {
					containerId: 'personal-menu-collapsible',
					containerClass: 'personal-menu-container',
					headerClass: 'personal-menu-header',
					contentClass: 'personal-menu-content',
					listClass: 'personal-menu-list',
					headerText: 'User',
					stateStorageKey: CONFIG.STORAGE_KEYS.PERSONAL_MENU_STATE,
					bodyClass: CONFIG.BODY_CLASSES.HAS_PERSONAL_MENU,
					itemIds: [
						'pt-login', 'pt-createaccount', 'pt-anonuserpage', 'pt-anontalk'
					]
				} );
			}

			this.initialized = true;
		},

		init: function () {
			this.initUserMenuCollapsible();
		}
	};

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

/*-------------------------------------------------------------------------------------------------------------Link Processor------------------------------------------------------------*/


importScript('MediaWiki:MeuScript.js');

( function () {
	'use strict';

	function ensureThemeStylesLoaded() {
		try {
			if ( !window.mw || !mw.loader || !mw.config ) {
				return;
			}

			const skin = mw.config.get( 'skin' ) || 'vector';
			const moduleName = 'themeloader.skins.' + skin + '.default';

			if ( typeof mw.loader.getState === 'function' ) {
				const state = mw.loader.getState( moduleName );
				if ( state === null ) {
					return;
				}
				if ( state === 'ready' || state === 'loading' ) {
					return;
				}
			}

			mw.loader.load( moduleName );
		} catch ( e ) {
			// Ignore error
		}
	}

	function getSavedTheme() {
		try {
			return localStorage.getItem( 'mw-theme' ) || 'default';
		} catch ( e ) {
			return 'default';
		}
	}

	// Define available themes matching christmas.css roots
	const themes = [
		{ name: 'Pink', id: 'default' },
		{ name: 'Dark', id: 'dark-neutral' },
		{ name: 'Christmas', id: 'christmas' },
		{ name: 'Power Light', id: 'power-light' },
		{ name: 'Magic Light', id: 'magic-light' },
		{ name: 'Sense Light', id: 'sense-light' },
		{ name: 'Charm Light', id: 'charm-light' },
		{ name: 'Power Dark', id: 'power-dark' },
		{ name: 'Charm Dark', id: 'charm-dark' },
		{ name: 'Magic Dark', id: 'dark-lightBlue' },
		{ name: 'Sense Dark', id: 'dark-purple' },
	];

	// Function to apply theme
	function applyTheme( themeId ) {
		ensureThemeStylesLoaded();

		try {
			localStorage.setItem( 'mw-theme', themeId );
		} catch ( e ) {
			// Ignore error
		}

		if ( themeId === 'default' ) {
			document.documentElement.removeAttribute( 'data-theme' );
		} else {
			document.documentElement.setAttribute( 'data-theme', themeId );
		}
	}

	// Initialize theme immediately
	ensureThemeStylesLoaded();
	applyTheme( getSavedTheme() );

	// Wait for DOM
	const domReady = function ( callback ) {
		if ( document.readyState === 'loading' ) {
			document.addEventListener( 'DOMContentLoaded', callback );
		} else {
			setTimeout( callback, 0 );
		}
	};

	domReady( function () {
		// Prevent duplicate injection
		if ( document.getElementById( 'mw-theme-floating' ) ) {
			return;
		}

		// Create Floating Container
		// ID matches CSS: #mw-theme-floating
		const container = document.createElement( 'div' );
		container.id = 'mw-theme-floating';

		// Create Inner Wrapper
		// Class matches CSS: .themeMenu
		const themeMenuDiv = document.createElement( 'div' );
		themeMenuDiv.className = 'themeMenu';

		// Create Toggle Button
		// Class matches CSS: .themeMenu-toggle
		const btn = document.createElement( 'button' );
		btn.className = 'themeMenu-toggle';
		btn.textContent = 'Appearence';
		btn.title = 'Change appearence';
		btn.type = 'button';
		
		// Create Dropdown List
		// Class matches CSS: .themeMenu-dropdown
		const dropdown = document.createElement( 'ul' );
		dropdown.className = 'themeMenu-dropdown';
		
		// Populate Items
		themes.forEach( function ( theme ) {
			// Item Wrapper
			// CSS doesn't strictly require this wrapper for floating, 
			// but theme-menu.css might use .themeMenu-itemWrap
			const itemWrap = document.createElement( 'li' );
			itemWrap.className = 'themeMenu-itemWrap';

			const themeLink = document.createElement( 'a' );
			themeLink.className = 'themeMenu-item';
			themeLink.href = '#';
			themeLink.textContent = theme.name;
			themeLink.dataset.themeId = theme.id;
			
			// Highlight current
			const currentTheme = getSavedTheme();
			if ( theme.id === currentTheme ) {
				themeLink.classList.add( 'is-current' );
			}
			
			themeLink.addEventListener( 'click', function ( e ) {
				e.preventDefault();
				ensureThemeStylesLoaded();
				applyTheme( theme.id );
				
				// Update highlighting
				dropdown.querySelectorAll( '.themeMenu-item' ).forEach( function ( link ) {
					link.classList.remove( 'is-current' );
				} );
				themeLink.classList.add( 'is-current' );

				// Close menu
				themeMenuDiv.classList.remove( 'is-open' );
			} );

			itemWrap.appendChild( themeLink );
			dropdown.appendChild( itemWrap );
		} );

		// Toggle Logic
		// CSS uses .themeMenu.is-open .themeMenu-dropdown { display: block }
		btn.addEventListener( 'click', function ( e ) {
			e.preventDefault();
			e.stopPropagation();
			themeMenuDiv.classList.toggle( 'is-open' );
		} );

		// Click Outside Logic
		document.addEventListener( 'click', function ( e ) {
			if ( !themeMenuDiv.contains( e.target ) ) {
				themeMenuDiv.classList.remove( 'is-open' );
			}
		} );

		// Assemble
		themeMenuDiv.appendChild( btn );
		themeMenuDiv.appendChild( dropdown );
		container.appendChild( themeMenuDiv );

		// Inject into Body
		document.body.appendChild( container );
	} );

}() );



(function () {
  'use strict';

  const CONFIG = {
    SELECTORS: {
      COLLAPSIBLE_HEADER: '.collapsible-header',
      COLLAPSIBLE_BUTTON: '.collapsible'
    },
    CLASSES: {
      ACTIVE: 'active',
      EXPANDED: 'expanded'
    }
  };

  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }

  const CollapsibleSections = {
    initCollapsibleHeaders: function () {
      const headers = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_HEADER);

      headers.forEach(header => {
        header.addEventListener('click', (event) => {
          event.preventDefault();

          const section = header.parentElement;
          const content = section.querySelector('.collapsible-content');

          if (content) {
            const isExpanded = section.classList.contains(CONFIG.CLASSES.EXPANDED);

            if (isExpanded) {
              section.classList.remove(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = '0';
            } else {
              section.classList.add(CONFIG.CLASSES.EXPANDED);
              content.style.maxHeight = content.scrollHeight + 'px';
            }
          }
        });
      });
    },

    initCollapsibleButtons: function () {
      const buttons = safeQuerySelectorAll(CONFIG.SELECTORS.COLLAPSIBLE_BUTTON);

      buttons.forEach(button => {
        button.addEventListener('click', (event) => {
          event.preventDefault();

          const content = button.nextElementSibling;

          if (content && content.classList.contains('collapsible-content')) {
            const isActive = button.classList.contains(CONFIG.CLASSES.ACTIVE);

            if (isActive) {
              button.classList.remove(CONFIG.CLASSES.ACTIVE);
              content.style.maxHeight = '0';
            } else {
              button.classList.add(CONFIG.CLASSES.ACTIVE);
              content.style.maxHeight = content.scrollHeight + 'px';
            }
          }
        });
      });
    },

    init: function () {
      this.initCollapsibleHeaders();
      this.initCollapsibleButtons();
    }
  };

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

/*
Propósito da funcionalidade:
- Oferecer um sistema de recomendação de “Guardian type” baseado em opções selecionadas pelo usuário.
- Controlar accordion de seções (`.guardian-accordion`) e permitir abrir diretamente por nível de dificuldade (1–5).
- Calcular a recomendação pela contagem de votos dos `.decision-options .option.selected`, resolvendo empates escolhendo o tipo mais “difícil”.
- Atualizar o texto em `#recommendedType` e destacar visualmente o tipo recomendado com a classe `recommended`.
- Expor funções globais para uso em HTML/conteúdo do wiki:
  - `window.updateGuardianRecommendation()`
  - `window.showGuardianByDifficulty(level)`
*/

(function () {
  'use strict';

  const CONFIG = {
    SELECTORS: {
      GUARDIAN_ACCORDION: '.guardian-accordion'
    },
    CLASSES: {
      ACTIVE: 'active',
      RECOMMENDED: 'recommended'
    },
    DELAYS: {
      ACCORDION_SCROLL_DELAY: 300
    }
  };

  function safeGetElementById(id) {
    try {
      return document.getElementById(id);
    } catch (error) {
      console.warn(`Element with ID '${id}' not found:`, error);
      return null;
    }
  }

  function safeQuerySelectorAll(selector, context = document) {
    try {
      return context.querySelectorAll(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return [];
    }
  }

  function safeQuerySelector(selector, context = document) {
    try {
      return context.querySelector(selector);
    } catch (error) {
      console.warn(`Invalid selector '${selector}':`, error);
      return null;
    }
  }

  function isElementInViewport(element) {
    if (!element) return false;

    const rect = element.getBoundingClientRect();
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );
  }

  const GuardianDecisionHelper = {
    DIFFICULTY_MAP: {
      '1': 'regular',
      '2': 'mighty',
      '3': 'legendary',
      '4': 'superior',
      '5': 'accomplished'
    },

    TYPE_DESCRIPTIONS: {
      'regular': 'Perfect for beginners with limited time',
      'mighty': 'Good balance of effort and power',
      'legendary': 'Excellent for endgame content',
      'superior': 'For dedicated players seeking optimization',
      'accomplished': 'For true perfectionists'
    },

    initAccordion: function () {
      const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);

      accordions.forEach(accordion => {
        accordion.addEventListener('click', function () {
          this.classList.toggle(CONFIG.CLASSES.ACTIVE);
          const panel = this.nextElementSibling;

          if (panel) {
            if (panel.style.maxHeight) {
              panel.style.maxHeight = null;
            } else {
              panel.style.maxHeight = panel.scrollHeight + 'px';
            }
          }
        });
      });
    },

    showGuardianByDifficulty: function (level) {
      const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);

      accordions.forEach(accordion => {
        accordion.classList.remove(CONFIG.CLASSES.ACTIVE);
        const panel = accordion.nextElementSibling;
        if (panel) {
          panel.style.maxHeight = null;
        }
      });

      const guardianType = this.DIFFICULTY_MAP[level];
      if (guardianType) {
        const targetAccordion = safeQuerySelector(`.${guardianType}-accordion`);
        if (targetAccordion) {
          targetAccordion.classList.add(CONFIG.CLASSES.ACTIVE);
          const panel = targetAccordion.nextElementSibling;
          if (panel) {
            panel.style.maxHeight = panel.scrollHeight + 'px';
          }

          setTimeout(() => {
            targetAccordion.scrollIntoView({
              behavior: 'smooth',
              block: 'center'
            });
          }, CONFIG.DELAYS.ACCORDION_SCROLL_DELAY);
        }
      }
    },

    initDecisionHelper: function () {
      const options = safeQuerySelectorAll('.decision-options .option');

      options.forEach(option => {
        option.addEventListener('click', function () {
          const siblings = this.parentElement.querySelectorAll('.option');
          siblings.forEach(sibling => sibling.classList.remove('selected'));

          this.classList.add('selected');

          GuardianDecisionHelper.updateRecommendation();
        });
      });
    },

    updateRecommendation: function () {
      const selectedOptions = safeQuerySelectorAll('.decision-options .option.selected');
      const recommendedTypeElement = safeGetElementById('recommendedType');

      if (!recommendedTypeElement) return;

      if (selectedOptions.length === 0) {
        recommendedTypeElement.textContent = 'Select options above';
        return;
      }

      const votes = {
        'regular': 0,
        'mighty': 0,
        'legendary': 0,
        'superior': 0,
        'accomplished': 0
      };

      selectedOptions.forEach(option => {
        const types = option.getAttribute('data-type');
        if (types) {
          types.split(',').forEach(type => {
            if (votes.hasOwnProperty(type.trim())) {
              votes[type.trim()]++;
            }
          });
        }
      });

      let maxVotes = 0;
      let recommendedType = '';
      let tiedTypes = [];

      for (const type in votes) {
        if (votes[type] > maxVotes) {
          maxVotes = votes[type];
          recommendedType = type;
          tiedTypes = [type];
        } else if (votes[type] === maxVotes && maxVotes > 0) {
          tiedTypes.push(type);
        }
      }

      if (tiedTypes.length > 1) {
        const difficultyOrder = ['regular', 'mighty', 'legendary', 'superior', 'accomplished'];
        let highestDifficultyIndex = -1;

        tiedTypes.forEach(type => {
          const typeIndex = difficultyOrder.indexOf(type);
          if (typeIndex > highestDifficultyIndex) {
            highestDifficultyIndex = typeIndex;
            recommendedType = type;
          }
        });
      }

      const formattedType = recommendedType.charAt(0).toUpperCase() + recommendedType.slice(1) + ' Guardian';
      const description = this.TYPE_DESCRIPTIONS[recommendedType] || '';
      const recommendationText = `${formattedType} - ${description}`;

      recommendedTypeElement.textContent = recommendationText;
      this.highlightRecommendedType(recommendedType);
    },

    highlightRecommendedType: function (type) {
      const accordions = safeQuerySelectorAll(CONFIG.SELECTORS.GUARDIAN_ACCORDION);
      accordions.forEach(accordion => {
        accordion.classList.remove(CONFIG.CLASSES.RECOMMENDED);
      });

      const recommendedAccordion = safeQuerySelector(`.${type}-accordion`);
      if (recommendedAccordion) {
        recommendedAccordion.classList.add(CONFIG.CLASSES.RECOMMENDED);

        if (!isElementInViewport(recommendedAccordion)) {
          recommendedAccordion.scrollIntoView({
            behavior: 'smooth',
            block: 'center'
          });
        }
      }
    },

    init: function () {
      const hasGuardianElements =
        safeQuerySelector(CONFIG.SELECTORS.GUARDIAN_ACCORDION) ||
        safeQuerySelector('.decision-options');

      if (hasGuardianElements) {
        this.initAccordion();
        this.initDecisionHelper();

        window.updateGuardianRecommendation = this.updateRecommendation.bind(this);
        window.showGuardianByDifficulty = this.showGuardianByDifficulty.bind(this);
      }
    }
  };

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






















/*
Propósito da funcionalidade:
- Converter marcação de links no estilo MediaWiki escrita como texto (ex.: `[[Titulo]]` ou `[[Titulo|Texto]]`)
  em links HTML reais (`<a href="...">`).
- Ignorar contextos onde essa conversão não deve acontecer (scripts, editores, formulários, etc.).
- Preservar links `File:`/`Image:` como texto, sem conversão.
- Gerar URLs usando `mw.util.getUrl()` quando disponível, com fallback para `index.php?title=...`.
*/

(function () {
  'use strict';

  function isInExcludedContext(node) {
    if (!node || !node.parentElement) return false;

    const excludedSelectors = [
      'script', 'style', 'textarea', 'input', 'select', 'option',
      '.ve-ui-surface', '.mw-editform', '.CodeMirror', '.cm-editor', '.ace_editor'
    ].join(', ');

    return !!node.parentElement.closest(excludedSelectors);
  }

  const WikiLinkProcessor = {
    processWikiLinks: function () {
      if (!document.body || document.body.textContent.indexOf('[[') === -1) {
        return;
      }

      const walker = document.createTreeWalker(
        document.body,
        NodeFilter.SHOW_TEXT,
        null,
        false
      );

      const textNodes = [];
      let node;

      while ((node = walker.nextNode())) {
        const value = node.nodeValue;
        if (!value || value.indexOf('[[') === -1 || isInExcludedContext(node)) {
          continue;
        }
        textNodes.push(node);
      }

      const linkRegex = /\[\[([^\|\]]+)(?:\|([^\]]+))?\]\]/g;

      textNodes.forEach(textNode => {
        const sourceText = textNode.nodeValue;
        if (!linkRegex.test(sourceText)) return;

        linkRegex.lastIndex = 0;

        const parent = textNode.parentNode;
        const fragment = document.createDocumentFragment();
        let lastIndex = 0;
        let match;

        while ((match = linkRegex.exec(sourceText)) !== null) {
          if (match.index > lastIndex) {
            fragment.appendChild(
              document.createTextNode(sourceText.slice(lastIndex, match.index))
            );
          }

          const rawTitle = (match[1] || '').trim();
          const displayText = (match[2] != null ? match[2] : rawTitle).trim();

          if (/^(File:|Image:)/i.test(rawTitle)) {
            // Handle File/Image links
            const cleanFilename = rawTitle.replace(/^(File:|Image:)/i, '').trim();
            const parts = displayText.split('|');
            let caption = '';
            let isThumb = false;
            
            // Basic parser for options (thumb, caption)
            // Assumes the last part is the caption if it's not a keyword
            // This is a simplified parser compared to full MediaWiki
            parts.forEach((part, index) => {
              const p = part.trim().toLowerCase();
              if (p === 'thumb' || p === 'thumbnail') {
                isThumb = true;
              } else if (['left', 'right', 'center', 'none', 'frame', 'frameless', 'border'].includes(p)) {
                // Alignment/Frame options - currently ignored or could be added as classes
              } else if (p.match(/^\d+px$/)) {
                 // Size option - currently ignored
              } else {
                // Assume it's caption (usually the last one, but we take the last non-keyword)
                caption = part.trim();
              }
            });

            // Use Special:FilePath to get the image source
            let src;
            if (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function') {
              src = window.mw.util.getUrl('Special:FilePath/' + cleanFilename);
            } else {
              // Fallback for local development or external usage
              // Use absolute URL to avoid local file:// or relative path issues which cause timeouts/delays
              src = 'https://mewsie.world/CoraTOWiki/index.php?title=Special:FilePath/' + encodeURIComponent(cleanFilename);
            }

            if (isThumb) {
              // Create structure: <div class="image-container"><img ...><div class="image-caption">...</div></div>
              const container = document.createElement('div');
              container.className = 'image-container';

              const img = document.createElement('img');
              img.setAttribute('src', src);
              img.setAttribute('alt', caption || cleanFilename);
              img.setAttribute('loading', 'lazy'); // Optimize loading
              img.setAttribute('decoding', 'async'); // Optimize decoding
              container.appendChild(img);

              if (caption) {
                const capDiv = document.createElement('div');
                capDiv.className = 'image-caption';
                capDiv.textContent = caption;
                container.appendChild(capDiv);
              }

              fragment.appendChild(container);
            } else {
              // Inline image or simple image
              const img = document.createElement('img');
              img.setAttribute('src', src);
              img.setAttribute('alt', caption || cleanFilename);
              img.setAttribute('loading', 'lazy'); // Optimize loading
              img.setAttribute('decoding', 'async'); // Optimize decoding
              fragment.appendChild(img);
            }
          } else {
            const cleanTitle = rawTitle.charAt(0) === ':' ? rawTitle.slice(1) : rawTitle;

            const href = (window.mw && window.mw.util && typeof window.mw.util.getUrl === 'function')
              ? window.mw.util.getUrl(cleanTitle)
              : ('index.php?title=' + encodeURIComponent(cleanTitle));

            const anchor = document.createElement('a');
            anchor.className = 'mw-link-internal';
            anchor.setAttribute('href', href);
            anchor.appendChild(document.createTextNode(displayText));
            fragment.appendChild(anchor);
          }

          lastIndex = linkRegex.lastIndex;
        }

        if (lastIndex < sourceText.length) {
          fragment.appendChild(
            document.createTextNode(sourceText.slice(lastIndex))
          );
        }

        parent.insertBefore(fragment, textNode);
        parent.removeChild(textNode);
      });
    },

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

  WikiLinkProcessor.init();
})();