function computeMenuTranslation(switcher, optionsElement) {
  // Calculate the position of a language options element.
  const switcherRect = switcher.getBoundingClientRect();

  // Must be called before optionsElement.clientWidth.
  optionsElement.style.minWidth = `${Math.max(switcherRect.width, 50)}px`;

  const isOnTop = switcher.dataset.location === 'top';
  const isOnBottom = switcher.dataset.location === 'bottom';
  const isOnBottomRight = switcher.dataset.location === 'bottom-right';
  const isRTL = document.documentElement.dir === 'rtl'

  // Stuck on the left side of the switcher.
  let x = switcherRect.left;

  if (isOnTop && !isRTL || isOnBottom && isRTL || isOnBottomRight && !isRTL) {
    // Stuck on the right side of the switcher.
    x = switcherRect.right - optionsElement.clientWidth;
  }

  // Stuck on the top of the switcher.
  let y = switcherRect.top - window.innerHeight - 10;

  if (isOnTop) {
    // Stuck on the bottom of the switcher.
    y = switcherRect.top - window.innerHeight + optionsElement.clientHeight + switcher.clientHeight + 4;
  }

  return { x: x, y: y };
}

function toggleMenu(switcher) {
  const optionsElement = switcher.nextElementSibling;

  optionsElement.classList.toggle('hx:hidden');

  // Calculate the position of a language options element.
  const translate = computeMenuTranslation(switcher, optionsElement);

  optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`;
}

function resizeMenu(switcher) {
  const optionsElement = switcher.nextElementSibling;

  if (optionsElement.classList.contains('hx:hidden')) return;

  // Calculate the position of a language options element.
  const translate = computeMenuTranslation(switcher, optionsElement);

  optionsElement.style.transform = `translate3d(${translate.x}px, ${translate.y}px, 0)`;
}

;
// Light / Dark theme toggle
(function () {
  const defaultTheme = 'system'
  const themes = ["light", "dark"];

  const themeToggleButtons = document.querySelectorAll(".hextra-theme-toggle");
  const themeToggleOptions = document.querySelectorAll(".hextra-theme-toggle-options p");

  function applyTheme(theme) {
    theme = themes.includes(theme) ? theme : "system";

    themeToggleButtons.forEach((btn) => btn.parentElement.dataset.theme = theme );

    localStorage.setItem("color-theme", theme);
  }

  function switchTheme(theme) {
    setTheme(theme);
    applyTheme(theme);
  }

  const colorTheme = "color-theme" in localStorage ? localStorage.getItem("color-theme") : defaultTheme;
  switchTheme(colorTheme);

  // Add click event handler to the menu items.
  themeToggleOptions.forEach((option) => {
    option.addEventListener("click", function (e) {
      e.preventDefault();

      switchTheme(option.dataset.item);
    })
  })

  // Add click event handler to the buttons
  themeToggleButtons.forEach((toggler) => {
    toggler.addEventListener("click", function (e) {
      e.preventDefault();

      toggleMenu(toggler);
    });
  });

  window.addEventListener("resize", () => themeToggleButtons.forEach(resizeMenu))

  // Dismiss the menu when clicking outside
  document.addEventListener('click', (e) => {
    if (e.target.closest('.hextra-theme-toggle') === null) {
      themeToggleButtons.forEach((toggler) => {
        toggler.dataset.state = 'closed';
        toggler.nextElementSibling.classList.add('hx:hidden');
      });
    }
  });

  // Listen for system theme changes
  window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
    if (localStorage.getItem("color-theme") === "system") {
      setTheme("system");
    }
  });
})();

;
//
;
// Hamburger menu for mobile navigation

document.addEventListener('DOMContentLoaded', function () {
  const menu = document.querySelector('.hextra-hamburger-menu');
  const sidebarContainer = document.querySelector('.hextra-sidebar-container');

  function toggleMenu() {
    // Toggle the hamburger menu
    menu.querySelector('svg').classList.toggle('open');

    // When the menu is open, we want to show the navigation sidebar
    sidebarContainer.classList.toggle('hx:max-md:[transform:translate3d(0,-100%,0)]');
    sidebarContainer.classList.toggle('hx:max-md:[transform:translate3d(0,0,0)]');

    // When the menu is open, we want to prevent the body from scrolling
    document.body.classList.toggle('hx:overflow-hidden');
    document.body.classList.toggle('hx:md:overflow-auto');
  }

  menu.addEventListener('click', (e) => {
    e.preventDefault();
    toggleMenu();
  });

  // Select all anchor tags in the sidebar container
  const sidebarLinks = sidebarContainer.querySelectorAll('a');

  // Add click event listener to each anchor tag
  sidebarLinks.forEach(link => {
    link.addEventListener('click', (e) => {
      // Check if the href attribute contains a hash symbol (links to a heading)
      if (link.getAttribute('href') && link.getAttribute('href').startsWith('#')) {
        // Only dismiss overlay on mobile view
        if (window.innerWidth < 768) {
          toggleMenu();
        }
      }
    });
  });
});

;
// Copy button for code blocks

document.addEventListener('DOMContentLoaded', function () {
  const getCopyIcon = () => {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.innerHTML = `
      <path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
    `;
    svg.setAttribute('fill', 'none');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2');
    return svg;
  }

  const getSuccessIcon = () => {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.innerHTML = `
      <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
    `;
    svg.setAttribute('fill', 'none');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('stroke', 'currentColor');
    svg.setAttribute('stroke-width', '2');
    return svg;
  }

  document.querySelectorAll('.hextra-code-copy-btn').forEach(function (button) {
    // Add copy and success icons
    button.querySelector('.hextra-copy-icon')?.appendChild(getCopyIcon());
    button.querySelector('.hextra-success-icon')?.appendChild(getSuccessIcon());

    // Add click event listener for copy button
    button.addEventListener('click', function (e) {
      e.preventDefault();
      // Get the code target
      const target = button.parentElement.previousElementSibling;
      let codeElement;
      if (target.tagName === 'CODE') {
        codeElement = target;
      } else {
        // Select the last code element in case line numbers are present
        const codeElements = target.querySelectorAll('code');
        codeElement = codeElements[codeElements.length - 1];
      }
      if (codeElement) {
        let code = codeElement.innerText;
        // Replace double newlines with single newlines in the innerText
        // as each line inside <span> has trailing newline '\n'
        if ("lang" in codeElement.dataset) {
          code = code.replace(/\n\n/g, '\n');
        }
        navigator.clipboard.writeText(code).then(function () {
          button.classList.add('copied');
          setTimeout(function () {
            button.classList.remove('copied');
          }, 1000);
        }).catch(function (err) {
          console.error('Failed to copy text: ', err);
        });
      } else {
        console.error('Target element not found');
      }
    });
  });
});

;
(function () {
  function updateGroup(container, index) {
    const tabs = Array.from(container.querySelectorAll('.hextra-tabs-toggle'));
    tabs.forEach((tab, i) => {
      tab.dataset.state = i === index ? 'selected' : '';
      if (i === index) {
        tab.setAttribute('aria-selected', 'true');
        tab.tabIndex = 0;
      } else {
        tab.removeAttribute('aria-selected');
        tab.removeAttribute('tabindex');
      }
    });
    const panelsContainer = container.parentElement.nextElementSibling;
    if (!panelsContainer) return;
    Array.from(panelsContainer.children).forEach((panel, i) => {
      panel.dataset.state = i === index ? 'selected' : '';
      if (i === index) {
        panel.tabIndex = 0;
      } else {
        panel.removeAttribute('tabindex');
      }
    });
  }

  const syncGroups = document.querySelectorAll('[data-tab-group]');

  syncGroups.forEach((group) => {
    const key = encodeURIComponent(group.dataset.tabGroup);
    const saved = localStorage.getItem('hextra-tab-' + key);
    if (saved !== null) {
      updateGroup(group, parseInt(saved, 10));
    }
  });

  document.querySelectorAll('.hextra-tabs-toggle').forEach((button) => {
    button.addEventListener('click', function (e) {
      const container = e.target.parentElement;
      const index = Array.from(container.querySelectorAll('.hextra-tabs-toggle')).indexOf(
        e.target
      );
      
      if (container.dataset.tabGroup) {
        // Sync behavior: update all tab groups with the same name
        const tabGroupValue = container.dataset.tabGroup;
        const key = encodeURIComponent(tabGroupValue);
        document
          .querySelectorAll('[data-tab-group="' + tabGroupValue + '"]')
          .forEach((grp) => updateGroup(grp, index));
        localStorage.setItem('hextra-tab-' + key, index.toString());
      } else {
        // Non-sync behavior: update only this specific tab group
        updateGroup(container, index);
      }
    });
  });
})();

;
(function () {
  const languageSwitchers = document.querySelectorAll('.hextra-language-switcher');

  languageSwitchers.forEach((switcher) => {
    switcher.addEventListener('click', (e) => {
      e.preventDefault();

      switcher.dataset.state = switcher.dataset.state === 'open' ? 'closed' : 'open';

      toggleMenu(switcher);
    });
  });

  window.addEventListener("resize", () => languageSwitchers.forEach(resizeMenu))

  // Dismiss language switcher when clicking outside
  document.addEventListener('click', (e) => {
    if (e.target.closest('.hextra-language-switcher') === null) {
      languageSwitchers.forEach((switcher) => {
        switcher.dataset.state = 'closed';
        const optionsElement = switcher.nextElementSibling;
        optionsElement.classList.add('hx:hidden');
      });
    }
  });
})();

;
(function () {
  const hiddenClass = "hx:hidden";
  const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle");

  dropdownToggles.forEach((toggle) => {
    toggle.addEventListener("click", (e) => {
      e.preventDefault();
      e.stopPropagation();

      // Close all other dropdowns first
      dropdownToggles.forEach((otherToggle) => {
        if (otherToggle !== toggle) {
          otherToggle.dataset.state = "closed";
          const otherMenuItems = otherToggle.nextElementSibling;
          otherMenuItems.classList.add(hiddenClass);
        }
      });

      // Toggle current dropdown
      const isOpen = toggle.dataset.state === "open";
      toggle.dataset.state = isOpen ? "closed" : "open";
      const menuItemsElement = toggle.nextElementSibling;

      if (!isOpen) {
        // Position dropdown centered with toggle
        menuItemsElement.style.position = "absolute";
        menuItemsElement.style.top = "100%";
        menuItemsElement.style.left = "50%";
        menuItemsElement.style.transform = "translateX(-50%)";
        menuItemsElement.style.zIndex = "1000";

        // Show dropdown
        menuItemsElement.classList.remove(hiddenClass);
      } else {
        // Hide dropdown
        menuItemsElement.classList.add(hiddenClass);
      }
    });
  });

  // Dismiss dropdown when clicking outside
  document.addEventListener("click", (e) => {
    if (e.target.closest(".hextra-nav-menu-toggle") === null) {
      dropdownToggles.forEach((toggle) => {
        toggle.dataset.state = "closed";
        const menuItemsElement = toggle.nextElementSibling;
        menuItemsElement.classList.add(hiddenClass);
      });
    }
  });

  // Close dropdowns on escape key
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape") {
      dropdownToggles.forEach((toggle) => {
        toggle.dataset.state = "closed";
        const menuItemsElement = toggle.nextElementSibling;
        menuItemsElement.classList.add(hiddenClass);
      });
    }
  });
})();

;
// Script for filetree shortcode collapsing/expanding folders used in the theme
// ======================================================================
document.addEventListener("DOMContentLoaded", function () {
  const folders = document.querySelectorAll(".hextra-filetree-folder");
  folders.forEach(function (folder) {
    folder.addEventListener("click", function () {
      Array.from(folder.children).forEach(function (el) {
        el.dataset.state = el.dataset.state === "open" ? "closed" : "open";
      });
      folder.nextElementSibling.dataset.state = folder.nextElementSibling.dataset.state === "open" ? "closed" : "open";
    });
  });
});

;
document.addEventListener("DOMContentLoaded", function () {
  scrollToActiveItem();
  enableCollapsibles();
});

function enableCollapsibles() {
  const buttons = document.querySelectorAll(".hextra-sidebar-collapsible-button");
  buttons.forEach(function (button) {
    button.addEventListener("click", function (e) {
      e.preventDefault();
      const list = button.parentElement.parentElement;
      if (list) {
        list.classList.toggle("open")
      }
    });
  });
}

function scrollToActiveItem() {
  const sidebarScrollbar = document.querySelector("aside.hextra-sidebar-container > .hextra-scrollbar");
  const activeItems = document.querySelectorAll(".hextra-sidebar-active-item");
  const visibleActiveItem = Array.from(activeItems).find(function (activeItem) {
    return activeItem.getBoundingClientRect().height > 0;
  });

  if (!visibleActiveItem) {
    return;
  }

  const yOffset = visibleActiveItem.clientHeight;
  const yDistance = visibleActiveItem.getBoundingClientRect().top - sidebarScrollbar.getBoundingClientRect().top;
  sidebarScrollbar.scrollTo({
    behavior: "instant",
    top: yDistance - yOffset
  });
}

;
// Back to top button

document.addEventListener("DOMContentLoaded", function () {
  const backToTop = document.querySelector("#backToTop");
  if (backToTop) {
    document.addEventListener("scroll", (e) => {
      if (window.scrollY > 300) {
        backToTop.classList.remove("hx:opacity-0");
      } else {
        backToTop.classList.add("hx:opacity-0");
      }
    });
  }
});

function scrollUp() {
  window.scroll({
    top: 0,
    left: 0,
    behavior: "smooth",
  });
}

;
/**
 * TOC Scroll - Highlights active TOC links based on visible headings
 * 
 * Uses Intersection Observer to track heading visibility and applies
 * 'hextra-toc-active' class to corresponding TOC links. Selects the
 * topmost heading when multiple are visible.
 * 
 * Requires: .hextra-toc element, matching heading IDs, toc.css styles
 */
document.addEventListener("DOMContentLoaded", function () {
  const toc = document.querySelector(".hextra-toc");
  if (!toc) return;

  const tocLinks = toc.querySelectorAll('a[href^="#"]');
  if (tocLinks.length === 0) return;

  const headingIds = Array.from(tocLinks).map((link) => link.getAttribute("href").substring(1));

  const headings = headingIds.map((id) => document.getElementById(decodeURIComponent(id))).filter(Boolean);
  if (headings.length === 0) return;

  let currentActiveLink = null;
  let isHashNavigation = false;

  // Create intersection observer
  const observer = new IntersectionObserver(
    (entries) => {
      // Skip observer updates during hash navigation
      if (isHashNavigation) return;

      const visibleHeadings = entries.filter((entry) => entry.isIntersecting).map((entry) => entry.target);

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

      // Find the heading closest to the top of the viewport
      const topMostHeading = visibleHeadings.reduce((closest, heading) => {
        const headingTop = heading.getBoundingClientRect().top;
        const closestTop = closest.getBoundingClientRect().top;
        return Math.abs(headingTop) < Math.abs(closestTop) ? heading : closest;
      });

      // Encode the id and make it lowercase to match the TOC link
      const targetId = encodeURIComponent(topMostHeading.id).toLowerCase();
      const targetLink = toc.querySelector(`a[href="#${targetId}"]`);

      if (targetLink && targetLink !== currentActiveLink) {
        // Remove active class from previous link
        if (currentActiveLink) {
          currentActiveLink.classList.remove("hextra-toc-active");
        }

        // Add active class to current link
        targetLink.classList.add("hextra-toc-active");
        currentActiveLink = targetLink;
      }
    },
    {
      rootMargin: "-20px 0px -60% 0px", // Adjust sensitivity
      threshold: [0, 0.1, 0.5, 1],
    }
  );

  // Observe all headings
  headings.forEach((heading) => observer.observe(heading));

  // Handle direct navigation to page with hash
  function handleHashNavigation() {
    const hash = window.location.hash; // already url encoded
    if (hash) {
      const targetLink = toc.querySelector(`a[href="${hash}"]`);
      if (targetLink) {
        // Disable observer temporarily during hash navigation
        isHashNavigation = true;

        if (currentActiveLink) {
          currentActiveLink.classList.remove("hextra-toc-active");
        }
        targetLink.classList.add("hextra-toc-active");
        currentActiveLink = targetLink;

        // Re-enable observer after scroll settles
        setTimeout(() => { isHashNavigation = false; }, 500);
        return;
      }
    }
  }

  // Handle hash changes navigation
  window.addEventListener("hashchange", handleHashNavigation);

  // Handle initial load
  setTimeout(handleHashNavigation, 100);
});

;
// 
(function () {
  const faviconEl = document.getElementById("favicon-svg");
  const faviconDarkExists = "false" === "true";

  if (faviconEl && faviconDarkExists) {
    const lightFavicon = '/favicon.svg';
    const darkFavicon = '/favicon-dark.svg';

    const darkModeQuery = window.matchMedia("(prefers-color-scheme: dark)");

    function updateFavicon(e) {
      faviconEl.href = e.matches ? darkFavicon : lightFavicon;
    }

    // Set favicon on load
    updateFavicon(darkModeQuery);

    // Listen for system preference changes
    darkModeQuery.addEventListener("change", updateFavicon);
  }
})();
