/*prevent FOUC on mobile when using sidebar */
.dwc-mobile :is(.bricks-is-frontend.brx-header-left, .bricks-is-frontend.brx-header-right) #brx-header {
  position: relative;
  inline-size: 100%;
  flex-direction: column;
}

.dwc-mobile .bricks-is-frontend:is(.brx-header-left, .brx-header-right) :is(#brx-content, #brx-footer) {
  margin-inline-start: 0;
}

/*prevent FOUC on desktop when using sidebar */
.bricks-is-frontend:is(.brx-header-left, .brx-header-right):not(.show-nav) #dwc_nav_wrapper {
  display: none;
}

/*=== sidebar css ===*/
html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header {
  flex-direction: column;
  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.7);  
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]).no-scroll {
  overflow: visible;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-menu.brxe-nav-nested .brx-nav-nested-items {
  max-block-size: 100dvb;
  padding-block-end: 12rem;
  overscroll-behavior: contain;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc_nav_wrapper {
  position: relative;
  overflow: hidden;
  block-size: 100%;
  transform: translateX(0%);
  visibility: visible;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #dwc-nest-menu .brxe-toggle {
  display: none !important;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-menu {
  display: flex;
  flex-direction: column;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-header > div {
  display: flex;
  grid-template-columns: 1fr;
  block-size: 100%;
  flex-direction: column;
  justify-content: flex-start;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-menu-wrap {
  inline-size: 100%;
  display: grid;
  grid-template-columns: 1fr;
  block-size: 100%;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header :is(.brxe-code, .dwc-nest-menu-overlay) {
  display: none;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-header {
  inline-size: 100%;
  padding-inline: 0;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #mobile_menu_top {
  min-block-size: var(--top-offset);
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu:not( [data-hide-close-bar = 'true']) .brx-dropdown-content {
  inset-block-start: calc(var(--top-offset) + 1px) !important;

}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu:not( [data-submenu-reveal = 'slide']) .brx-dropdown-content:not([data-submenu-reveal = 'slide'] *) {  
  inset-inline-start: 0;
  inset-block-start: unset !important;
  overflow: hidden;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu:not([data-submenu-reveal = 'slide']) .brxe-dropdown:not(.open.active) > .brx-dropdown-content > .brxe-dropdown:not([data-submenu-reveal = 'slide'] *){
  visibility: hidden;  
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-menu.brxe-nav-nested.brx-open .brxe-dropdown > .brx-dropdown-content {
  overscroll-behavior: contain;
  min-inline-size: var(--mobile-menu-width);

}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu .brxe-dropdown.open > .brx-submenu-toggle button:not([data-submenu-reveal = 'expand'] button)  {
  min-block-size: calc(var(--top-offset) - 1px);
  inset-block-start: 0;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu .brxe-dropdown.open[data-submenu-reveal = 'slide'] > .brx-submenu-toggle button  {
  min-block-size: calc(var(--top-offset) - 1px);
  inset-block-start: 0;
}

html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header #dwc-nest-menu .brxe-dropdown .brx-submenu-toggle button {
  min-block-size: 0;    
}

/* sidebar css ends*/



/*sidebar in builder*/
/*=== sidebar css ===*/
:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header {
  flex-direction: column;
  box-shadow: 0px 0px 2px rgba(0, 0, 0, 0.7);
  --top-offset: 40px;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-menu.brxe-nav-nested .brx-nav-nested-items {
  max-block-size: 100dvb;
  padding-block-end: 12rem;
  overscroll-behavior: contain;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #dwc_nav_wrapper {
  position: relative;
  overflow: hidden;
  block-size: 100%;
  transform: translateX(0%);
  visibility: visible;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #dwc-nest-menu .brxe-toggle {
  display: none !important;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-menu {
  display: flex;
  flex-direction: column;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-header > div {
  display: flex;
  grid-template-columns: 1fr;
  block-size: 100%;
  flex-direction: column;
  justify-content: flex-start;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-menu-wrap {
  inline-size: 100%;
  display: grid;
  grid-template-columns: 1fr;
  block-size: 100%;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-header {
  inline-size: 100%;
  padding-inline: 0;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #mobile_menu_top {
  min-block-size: var(--top-offset);
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #dwc-nest-menu .brx-dropdown-content {
  inset-block-start: calc(var(--top-offset) - 1px) !important;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-menu.brxe-nav-nested.brx-open .brxe-dropdown > .brx-dropdown-content {
  overscroll-behavior: contain;  
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #dwc-nest-menu .brxe-dropdown.open > .brx-submenu-toggle button {
  min-block-size: calc(var(--top-offset) - 1px);
  inset-block-start: 0;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #dwc-nest-menu .brxe-dropdown .brx-submenu-toggle button {
  min-block-size: 0;    
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] .dwc-nest-menu {
  margin: 0 !important;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] .brx-nav-nested-items {  
  flex-direction: column !important;  
}
   
:is(.brx-header-left, .brx-header-right)[data-builder-window] .dwc-nest-toggle--open.brxe-toggle {
  display: flex !important;
}
 
:is(.brx-header-left, .brx-header-right)[data-builder-window] .dwc-nest-menu .brx-nav-nested-items {
  position: relative !Important;
  background: var(--mobile-menu-bg) !important;
  align-items: stretch;
  flex: 1;
}
 
/* MENU CTA (LAST BUTTON) */
:is(.brx-header-left, .brx-header-right)[data-builder-window] [data-last-item-is-button="true"].dwc-nest-menu .brx-nav-nested-items > .menu-item:last-of-type {
  padding-inline: var(--menu-item-inline-padding) !important;
  padding-block: var(--menu-item-block-padding) !important;
}
 
:is(.brx-header-left, .brx-header-right)[data-builder-window] .dwc-nest-menu-top {
  min-block-size: 80px !important;
}
 
:is(.brx-header-left, .brx-header-right)[data-builder-window] .dwc-nest-nav-items {
  overflow-y: scroll;
}

:is(.brx-header-left, .brx-header-right)[data-builder-mode] .brx-dropdown-content {
  min-inline-size: var(--mobile-menu-width);
  position: static;
}

:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header .dwc-nest-menu.brxe-nav-nested .brx-nav-nested-items {
  flex-wrap: nowrap;
}
:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header #dwc-nest-menu .brx-dropdown-content {   
  visibility: visible !important;
  opacity: 1;
}

/*OVERLAY SIDEBAR*/


html:not(.dwc-mobile):has([data-overlay-sidebar=true])  {
 --mobile-menu-bg: rgb(255 255 255 / 0%);
  --menu-item-border: solid 1px rgb(255 255 255 / 50%);
 
}

html:not(.dwc-mobile):has([data-overlay-sidebar=true]) :is(.brx-header-left, .brx-header-right):not([data-builder-modee]) :is(main, footer){
  margin: 0 !important
}

html:not(.dwc-mobile):has([data-overlay-sidebar=true]) :is(.brx-header-left, .brx-header-right):not([data-builder-modee]) :is(main, footer) :where(section):not(section>section) {
  padding-inline-start: calc(var(--mobile-menu-width) + clamp(1.5rem, calc(0.625vw + 1.375rem), 1.875rem));
  max-inline-size: 100%
}

html:not(.dwc-mobile):has([data-overlay-sidebar=true]) :is(.brx-header-left, .brx-header-right):not([data-builder-modee]) #brx-header {
  border-radius: var(--overlay-sidebar-radius);
  overflow: hidden;
  background: var(--overlay-sidebar-bg);
  box-shadow: var(--overlay-sidebar-shadow) !important;
  inset: var(--overlay-sidebar-inset);
}

html:not(.dwc-mobile):has([data-overlay-sidebar=true]) :is(.brx-header-left, .brx-header-right):not([data-builder-modee]) .dwc-nest-header{
  backdrop-filter: blur(13px);
  background: transparent !important;
}

html:not(.dwc-mobile):has([data-overlay-sidebar=true]):not([data-builder-modee])  .brx-dropdown-content {
   background-color: rgb(255 255 255 / 100%);
}

/*NO BRX-OPEN STYLES*/
html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) #brx-header .dwc-nest-menu.brxe-nav-nested .brx-nav-nested-items {
  display: flex;
  flex-wrap: nowrap;
  flex-direction: column !important;
}


  html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) .dwc-nest-menu.brxe-nav-nested .brxe-dropdown .brx-dropdown-content {
      visibility: visible;
      min-inline-size: var(--mobile-menu-width) !important;
  }


  html:not(.dwc-mobile) :is(.brx-header-left, .brx-header-right):not([data-builder-window]) .dwc-nest-menu[data-submenu-reveal="expand"] .brxe-dropdown.open>.brx-dropdown-content {
   position: static;
}
class SidebarNavigation {
  constructor(options = {}) {
    // Basic configuration properties
    this.config = {
      minWidth: options.minWidth || MegaMenuCONFIG.minWidth, // Using external minWidth variable
      menuSelector: options.menuSelector || '.dwc-nest-menu',
      openClass: options.openClass || 'brx-open',
      activeClasses: options.activeClasses || ['open', 'active'],
      leftHeaderClass: options.leftHeaderClass || 'brx-header-left',
      rightHeaderClass: options.rightHeaderClass || 'brx-header-right',
      debounceDelay: options.debounceDelay || 100,
      menuItemClickDelay: options.menuItemClickDelay || 300
    };
    
    // Set dependent selectors
    const menuSelector = this.config.menuSelector;
    this.config.submenuToggleSelector = options.submenuToggleSelector || `${menuSelector} .brx-submenu-toggle`;
    this.config.dropdownSelector = options.dropdownSelector || `${menuSelector} .brxe-dropdown`;
    this.config.dropdownContentSelector = options.dropdownContentSelector || `${menuSelector} .brx-dropdown-content`;
    
    // State
    this.previousHeaderClass = null;
    this.dropdownClickHandlers = new Map();
    this.menuHoverHandlers = null;
    this.menuItemClickTimeout = null;
    this.keyboardNavHandler = null;
    this.cachedFocusableElements = null;
    this.cachedElements = {
      menuElement: null,
      navElement: null,
      dropdowns: null,
      dropdownToggles: null,
      menuItems: null
    };
    
    // Bind methods to this instance
    this.handleResize = this.debounce(this.handleMenu.bind(this), this.config.debounceDelay);
    this.handleOutsideClick = this.handleOutsideClick.bind(this);
  }
  
  // Initialize everything - called once
  init() {
    // Wait for DOM to be fully loaded before attaching events
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', () => {
        this.initAfterDOMLoaded();
      }, { once: true });
    } else {
      this.initAfterDOMLoaded();
    }
    
    return this;
  }
  
  // Separate initialization method to run after DOM is loaded
  initAfterDOMLoaded() {
    // Cache DOM elements once
    this.cacheElements();
    
    // Setup resize event with passive flag
    window.addEventListener('resize', this.handleResize, { passive: true });
    
    // Setup mutation observer for critical class changes only
    this.setupMutationObserver();
    
    // Initial setup based on current screen size
    this.handleMenu();
    
    // Cache focusable elements once if header class is present
    if (this.hasHeaderClass()) {
      this.cacheFocusableElements();
      this.setupMenuFocusNavigation();
    }
  }
  
  // Cache all required DOM elements upfront
  cacheElements() {
    this.cachedElements.menuElement = document.querySelector(this.config.menuSelector);
    
    if (this.cachedElements.menuElement) {
      this.cachedElements.navElement = this.cachedElements.menuElement.querySelector('.dwc-nest-nav-items');
      this.cachedElements.dropdowns = Array.from(document.querySelectorAll(this.config.dropdownSelector));
      this.cachedElements.dropdownToggles = Array.from(document.querySelectorAll(this.config.submenuToggleSelector));
      this.cachedElements.menuItems = Array.from(document.querySelectorAll(`${this.config.menuSelector} .menu-item`));
    }
  }
  
  // Set up a focused mutation observer only for dropdown state changes
  setupMutationObserver() {
    if (!this.cachedElements.dropdowns || this.cachedElements.dropdowns.length === 0) return;
    
    const callback = (mutations) => {
      for (let mutation of mutations) {
        if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
          const target = mutation.target;
          const prevClassList = mutation.oldValue ? mutation.oldValue.split(' ') : [];
          const hadBothBefore = prevClassList.includes('open') && prevClassList.includes('active');
          const hasBothNow = target.classList.contains('open') && target.classList.contains('active');
    
          if (hadBothBefore !== hasBothNow) {
            this.updateDropdownAccessibility();
            break; // Only need to update once per batch
          }
        }
      }
    };
    
    // Create observer with optimized options
    this.classObserver = new MutationObserver(callback);
    
    // Observe only the dropdown elements
    this.cachedElements.dropdowns.forEach(dropdown => {
      this.classObserver.observe(dropdown, { 
        attributes: true, 
        attributeFilter: ['class'], 
        attributeOldValue: true 
      });
    });
  }
  
  // Cache focusable elements for keyboard navigation
  cacheFocusableElements() {
    if (!this.cachedElements.navElement) return;
    
    // Get direct children of nav
    const directChildren = Array.from(this.cachedElements.navElement.children);
    
    // Find the first focusable element within each direct child
    this.cachedFocusableElements = directChildren.map(child => {
      // Check if the child itself is focusable
      if (child.matches('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])')) {
        return child;
      }
      // Otherwise, find the first focusable element within this child
      return child.querySelector('a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])');
    }).filter(Boolean); // Remove null/undefined values
  }
  
  // Clean up all event listeners and observers
  destroy() {
    // Clean up the mutation observer
    if (this.classObserver) {
      this.classObserver.disconnect();
      this.classObserver = null;
    }
    
    // Clean up resize listener
    window.removeEventListener('resize', this.handleResize);
    
    // Clean up click handlers
    if (this.dropdownClickHandlers.size > 0) {
      this.dropdownClickHandlers.forEach((handler, toggle) => {
        toggle.removeEventListener('click', handler);
      });
      this.dropdownClickHandlers.clear();
    }
    
    // Clean up hover handlers
    this.cleanupMenuHover();
    
    // Clean up menu item click handlers
    this.cleanupMenuItemClicks();
    
    // Clean up outside click handler
    document.removeEventListener('click', this.handleOutsideClick);
    
    // Clean up keyboard navigation
    if (this.keyboardNavHandler) {
      document.removeEventListener('keydown', this.keyboardNavHandler);
      this.keyboardNavHandler = null;
    }
    
    // Clear any pending timeouts
    if (this.menuItemClickTimeout) {
      clearTimeout(this.menuItemClickTimeout);
      this.menuItemClickTimeout = null;
    }
  }
  
  // Utility methods
  hasHeaderClass() {
    return document.body.classList.contains(this.config.leftHeaderClass) || 
           document.body.classList.contains(this.config.rightHeaderClass);
  }
  
  debounce(func, delay) {
    let timer;
    return (...args) => {
      clearTimeout(timer);
      timer = setTimeout(() => func(...args), delay);
    };
  }

  // Check if an element has all the required active classes
  hasAllActiveClasses(element) {
    return this.config.activeClasses.every(className => element.classList.contains(className));
  }
  
  // Toggle all active classes on an element
  toggleActiveClasses(element) {
    this.config.activeClasses.forEach(className => {
      element.classList.toggle(className);
    });
  }
  
  // Core functionality methods
  handleMenu() {
    if (!this.cachedElements.menuElement) return;
    if (!this.hasHeaderClass() && !this.previousHeaderClass) return;

    const screenWidth = window.innerWidth;
    const isLargeScreen = screenWidth >= this.config.minWidth;
    const menuElement = this.cachedElements.menuElement;

    if (!isLargeScreen) {
      // Save which class was present before removal
      if (this.hasHeaderClass()) {
        this.previousHeaderClass = document.body.classList.contains(this.config.leftHeaderClass) 
          ? this.config.leftHeaderClass 
          : this.config.rightHeaderClass;
        
        // Remove header classes
        document.body.classList.remove(this.config.leftHeaderClass, this.config.rightHeaderClass);
        menuElement.classList.remove(this.config.openClass);
        
        // Reset accessibility attributes
        this.resetAccessibilityAttributes();
      }
      
      // Clean up event handlers for mobile view
      this.cleanupMenuHover();
      this.cleanupMenuItemClicks();
      this.cleanupDropdownHandlers();
      document.removeEventListener('click', this.handleOutsideClick);
      
      return;
    }

    // Large screen behavior
    if (!this.hasHeaderClass() && this.previousHeaderClass) {
      document.body.classList.add(this.previousHeaderClass);
    }

    if (this.hasHeaderClass()) {
      if (!menuElement.classList.contains(this.config.openClass)) {
        menuElement.classList.add(this.config.openClass);
      }
      
      // Setup elements for large screen view
      this.setupMenuHover();
      this.setupMenuItemClicks();
      this.setupDropdownHandlers();
      this.setupMenuFocusNavigation();
      this.updateDropdownAccessibility();
      
      // Ensure outside click handler is set up
      document.removeEventListener('click', this.handleOutsideClick);
      document.addEventListener('click', this.handleOutsideClick, { passive: false });
    }
  }
  
  // Reset accessibility attributes when switching to mobile
  resetAccessibilityAttributes() {
    if (!this.cachedElements.dropdowns) return;
    
    // Remove all inert attributes from dropdown contents
    this.cachedElements.dropdowns.forEach(dropdown => {
      const content = dropdown.querySelector(this.config.dropdownContentSelector);
      if (content) {
        content.removeAttribute('inert');
      }
      
      const button = dropdown.querySelector('button');
      if (button) {
        button.setAttribute('aria-expanded', 'false');
      }
    });
  }
  
  setupMenuFocusNavigation() {
    // Only run if hasHeaderClass() is true and we have focusable elements
    if (!this.hasHeaderClass() || !this.cachedFocusableElements || this.cachedFocusableElements.length === 0) {
      return;
    }
    
    // Clean up previous handler if it exists
    if (this.keyboardNavHandler) {
      document.removeEventListener('keydown', this.keyboardNavHandler, true);
      this.keyboardNavHandler = null;
    }
    
    const navMenu = this.cachedElements.menuElement;
    const focusableElements = this.cachedFocusableElements;
    const firstFocusableElement = focusableElements[0];
    const lastFocusableElement = focusableElements[focusableElements.length - 1];
    
    // Find adjacent focusable elements outside the menu (only once during setup)
    const headerElement = navMenu.closest('header') || document.querySelector('header');
    
    // Prepare variables to hold adjacent elements
    let prevFocusableElement = null;
    let nextFocusableElement = null;
    let firstElementAfterHeader = null;
    
    if (headerElement) {
      // Get all focusable elements within the header - do this once and cache the result
      const headerFocusables = Array.from(
        headerElement.querySelectorAll('a:not([tabindex="-1"]), button:not([tabindex="-1"]), input:not([tabindex="-1"]), select:not([tabindex="-1"]), textarea:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])')
      ).filter(el => window.getComputedStyle(el).display !== 'none');
      
      // Find the index of our first and last menu elements in one pass
      const menuStartIndex = headerFocusables.indexOf(firstFocusableElement);
      const menuEndIndex = headerFocusables.indexOf(lastFocusableElement);
      
      // Cache the adjacent elements
      if (menuStartIndex > 0) {
        prevFocusableElement = headerFocusables[menuStartIndex - 1];
      }
      
      if (menuEndIndex !== -1 && menuEndIndex < headerFocusables.length - 1) {
        nextFocusableElement = headerFocusables[menuEndIndex + 1];
      }
      
      // Pre-calculate the first element after header - but only if needed
      if (!nextFocusableElement) {
        // Use a more efficient selector that targets immediate children of body that aren't the header
        const selector = 'body > *:not(header)';
        const nonHeaderElements = document.querySelectorAll(selector);
        
        // Only process if we have elements
        if (nonHeaderElements.length > 0) {
          // Create a function to find the first focusable element (used later if needed)
          this.findFirstFocusableAfterHeader = () => {
            for (const element of nonHeaderElements) {
              const focusable = element.querySelector('a:not([tabindex="-1"]), button:not([tabindex="-1"]), input:not([tabindex="-1"]), select:not([tabindex="-1"]), textarea:not([tabindex="-1"]), [tabindex]:not([tabindex="-1"])');
              if (focusable && window.getComputedStyle(focusable).display !== 'none') {
                return focusable;
              }
            }
            return null;
          };
        }
      }
    }
    
    // Create keyboard navigation handler with closure over the cached elements
    this.keyboardNavHandler = (e) => {
      // Quick check for Tab key first
      if (e.key !== 'Tab') return;
      
      // Then check if focus is inside the menu
      if (!navMenu.contains(document.activeElement)) return;
      
      let targetElement = null;
      
      // Handle tab navigation at boundaries only
      if (!e.shiftKey && document.activeElement === lastFocusableElement) {
        // Forward tab from last element
        e.preventDefault();
        e.stopPropagation();
        
        if (nextFocusableElement) {
          targetElement = nextFocusableElement;
        } else if (this.findFirstFocusableAfterHeader) {
          // Only search for elements after header if needed and not already found
          firstElementAfterHeader = this.findFirstFocusableAfterHeader();
          targetElement = firstElementAfterHeader;
        }
        
        // Focus on the target or body as fallback
        setTimeout(() => {
          if (targetElement) {
            targetElement.focus();
          } else {
            document.body.setAttribute('tabindex', '-1');
            document.body.focus();
            document.body.removeAttribute('tabindex');
          }
        }, 10);
      } 
      else if (e.shiftKey && document.activeElement === firstFocusableElement) {
        // Backward tab from first element
        e.preventDefault();
        e.stopPropagation();
        
        setTimeout(() => {
          if (prevFocusableElement) {
            prevFocusableElement.focus();
          } else {
            document.body.setAttribute('tabindex', '-1');
            document.body.focus();
            document.body.removeAttribute('tabindex');
          }
        }, 10);
      }
    };
    
    // Use capture phase for the event
    document.addEventListener('keydown', this.keyboardNavHandler, true);
  }
  
  setupMenuHover() {
    const menuElement = this.cachedElements.menuElement;
    if (!menuElement) return;
    
    // Clean up existing hover handlers first
    this.cleanupMenuHover();
    
    // Create event handlers
    const mouseenterHandler = () => {
      menuElement.classList.add(this.config.openClass);
    };
    
    const mouseleaveHandler = () => {
      menuElement.classList.remove(this.config.openClass);
    };
    
    // Add event listeners with passive flag for better performance
    menuElement.addEventListener('mouseenter', mouseenterHandler, { passive: true });
    menuElement.addEventListener('mouseleave', mouseleaveHandler, { passive: true });
    
    // Store the handlers for cleanup
    this.menuHoverHandlers = {
      element: menuElement,
      mouseenter: mouseenterHandler,
      mouseleave: mouseleaveHandler
    };
  }
  
  cleanupMenuHover() {
    if (this.menuHoverHandlers) {
      const { element, mouseenter, mouseleave } = this.menuHoverHandlers;
      element.removeEventListener('mouseenter', mouseenter);
      element.removeEventListener('mouseleave', mouseleave);
      this.menuHoverHandlers = null;
    }
  }
  
  setupMenuItemClicks() {
    if (!this.cachedElements.menuItems || this.cachedElements.menuItems.length === 0) return;
    
    // Clean up existing handlers first
    this.cleanupMenuItemClicks();
    
    const menuElement = this.cachedElements.menuElement;
    const menuItemHandlers = new Map();
    
    this.cachedElements.menuItems.forEach(item => {
      const clickHandler = () => {
        if (this.hasHeaderClass()) {
          // Clear any existing timeout
          if (this.menuItemClickTimeout) {
            clearTimeout(this.menuItemClickTimeout);
          }
          
          // Set timeout before adding the class
          this.menuItemClickTimeout = setTimeout(() => {
            if (!menuElement.classList.contains(this.config.openClass)) {
              menuElement.classList.add(this.config.openClass);
            }
          }, this.config.menuItemClickDelay);
        }
      };
      
      menuItemHandlers.set(item, clickHandler);
      item.addEventListener('click', clickHandler);
    });
    
    this.menuItemClickHandlers = menuItemHandlers;
  }
  
  cleanupMenuItemClicks() {
    if (this.menuItemClickHandlers && this.menuItemClickHandlers instanceof Map) {
      this.menuItemClickHandlers.forEach((handler, item) => {
        item.removeEventListener('click', handler);
      });
      this.menuItemClickHandlers.clear();
    }
    
    if (this.menuItemClickTimeout) {
      clearTimeout(this.menuItemClickTimeout);
      this.menuItemClickTimeout = null;
    }
  }
  
  setupDropdownHandlers() {
    if (!this.hasHeaderClass() || !this.cachedElements.dropdownToggles) return;
    
    // Clean up existing handlers first
    this.cleanupDropdownHandlers();
    
    this.cachedElements.dropdownToggles.forEach(toggle => {
      const clickHandler = (event) => {
        event.stopPropagation();
        event.preventDefault();
        
        const dropdown = toggle.closest(this.config.dropdownSelector);
        if (dropdown) {
          this.toggleActiveClasses(dropdown);
          this.updateDropdownAccessibility();
        }
      };
      
      this.dropdownClickHandlers.set(toggle, clickHandler);
      toggle.addEventListener('click', clickHandler);
    });
  }
  
  cleanupDropdownHandlers() {
    if (this.dropdownClickHandlers.size > 0) {
      this.dropdownClickHandlers.forEach((handler, toggle) => {
        toggle.removeEventListener('click', handler);
      });
      this.dropdownClickHandlers.clear();
    }
  }
  
  handleOutsideClick(event) {
    if (event.target.tagName === 'A') return;
    if (!event.target.closest(this.config.dropdownSelector)) return;
    if (!event.target.closest(this.config.submenuToggleSelector)) {
      event.preventDefault();
      event.stopPropagation();
    }
  }
  
  updateDropdownAccessibility() {
    // Only run if hasHeaderClass() is true
    if (!this.hasHeaderClass() || !this.cachedElements.dropdowns) return;
    
    this.cachedElements.dropdowns.forEach(dropdown => {
      const content = dropdown.querySelector(this.config.dropdownContentSelector);
      const button = dropdown.querySelector('button');
      
      // Check if dropdown has all active classes
      const isOpen = this.hasAllActiveClasses(dropdown);
      
      if (content) {
        if (isOpen) {
          content.removeAttribute('inert');
        } else {
          content.setAttribute('inert', '');
        }
      }
      
      if (button) {
        button.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
      }
    });
  }
}


const sidebarNav = new SidebarNavigation().init();

Upcoming WSDC Events

New York Flow Festival is a WSDC-sanctioned event. See other WSDC events scheduled throughout the year across the world.
Region:
Upcoming WSDC West Coast Swing events with points tiers per division, grouped by month
Dates Event J&J
Entries
LeadersFollowers

July 2026

7/22–26 St. Petersburg WCS Nights
St.Petersburg, Russia
355 2025 Tier 459 Tier 472
7/23–26 Florida Dance Magic
Coral Springs, FL
253 2025 Tier 337 Tier 445
7/23–26 Infinite Swing
Munich, Germany
Trial Event
7/25–8/1 Sea Sun and Swing
La Grande Motte, France
402 2024 Tier 446 Tier 470
7/30–8/2 Warsaw Summer Nights Westival
Warsaw, Poland
354 2025 Tier 464 Tier 477
7/30–8/2 Lisbon Westie Fest
Loures, Portugal
325 2025 Tier 455 Tier 475
7/31–8/2 Arizona Dance Classic
Phoenix, AZ
160 2025 Tier 327 Tier 338

August 2026

8/5–9 German Open WCS
Freiburg, Germany
787 2025 Tier 5123 Tier 6138
8/6–9 New England Dance Festival
Newton, MA
214 2025 Tier 327 Tier 443
8/6–9 Swing Fling
Herdon, VA
573 2025 Tier 478 Tier 594
8/6–9 Swingtacular: The Galactic Open
Burlingame, CA
724 2025 Tier 5125 Tier 6138
8/14–17 Grand Party Sofia (GPS)
Sofia, Bulgaria
210 2025 Tier 331 Tier 331
8/14–17 UpTown Swing
Upplands Väsby, Sweden
449 2025 Tier 581 Tier 584
8/20–23 Summer Hummer
Woburn, MA
449 2025 Tier 449 Tier 582
8/20–23 Shakedown Swing
Christchurch, New Zealand
114 2025 Tier 331 Tier 338
8/20–23 Swing Creation Hamburg
Hamburg, Germany
Trial Event
8/20–23 The Bend Connection
Bend, OR
197 2025 Tier 335 Tier 450
8/21–23 Westie Joy
Bucharest, Romania
Trial Event
8/21–23 Manneken Swing
Brussels, Belgium
Trial Event
8/21–23 Chicagoland Country and Swing Dance Festival
Rosemont, IL
179 2025 Tier 322 Tier 328
8/27–30 Desert City Swing
Phoenix, AZ
564 2022 Tier 581 Tier 5116
8/27–31 Rolling Swing
Bron, France
455 2025 Tier 462 Tier 599
8/28–30 Bristol Swing Fiesta
Bristol, United Kingdom
281 2025 Tier 450 Tier 453

September 2026

9/3–7 South Bay Dance Fling
San Jose, CA
265 2025 Tier 442 Tier 454
9/3–6 Korea Westival
Seogwipo-si, South Korea
232 2025 Tier 335 Tier 449
9/4–7 Jax Westie Fest
Jacksonville, FL
209 2024 Tier 219 Tier 332
9/10–13 Trilogy Swing
Raleigh, NC
532 2025 Tier 464 Tier 580
9/10–13 SwingTime Denver
Denver, CO
506 2025 Tier 473 Tier 584
9/10–14 Bavarian Open West Coast Swing Championships
Munich, Germany
556 2025 Tier 474 Tier 587
9/11–13 Sea Dance Fest
Ognikovo, Russia
143 2025 Tier 324 Tier 323
9/17–21 WCS Party in Vienna
Vienna, Austria
309 2025 Tier 443 Tier 464
9/17–20 Retaliation Swing
Seattle, WA
311 2025 Tier 447 Tier 461
9/18–21 Finnfest
Helsinki, Finland
541 2025 Tier 582 Tier 5100
9/18–21 Global Grand Prix -- West Coast Swing Championships
Paris, France
Trial Event
9/18–20 Austin Rocks
Austin, TX
382 2025 Tier 459 Tier 462
9/23–27 The Aloha Open
Wailea, HI
93 2025 Tier 219 Tier 326
9/24–28 Meet Me In St Louis
St. Louis, MO
329 2024 Tier 338 Tier 445
9/24–27 Calgary Town Open
Calgary, Canada
9/24–28 Midland Swing Open
London, United Kingdom
443 2025 Tier 463 Tier 580
9/24–27 Best of the Best WCS
Ashfield, Australia
55 2025 Tier 212 · 
9/24–28 Mooseland Swing 2026
Östersund, Sweden
239 2025 Tier 456 Tier 467
9/25–27 Philly Swing Classic
Wilmington, DE
180 2025 Tier 320 Tier 334

October 2026

10/1–4 Atlanta Swing Classic
Atlanta, GA
689 2025 Tier 599 Tier 5114
10/1–5 Westie Harbor
Gothenburg, Sweden
Trial Event
10/1–4 Norwegian Open WCS
Hamar, Norway
227 2025 Tier 446 Tier 338
10/1–5 RiverSwingNights
Dresden, Germany
Trial Event
10/2–5 Autumn Beat
Riga, Latvia
Trial Event
10/8–12 Boogie By The Bay
Burlingame, CA
616 2025 Tier 585 Tier 5100
10/9–11 Montreal Westie Fest
Montréal, Canada
308 2024 Tier 450 Tier 469
10/15–17 WCS Festival
Duesseldorf, Germany
358 2025 Tier 453 Tier 460
10/15–18 Paradise dance Festival
Irvine, CA
88 2025 Tier 338 Tier 450
10/15–18 Milan Modern Swing
Sesto San Giovanni, Italy
633 2025 Tier 593 Tier 599
10/15–18 The New Zealand West Coast Swing Open
Auckland, New Zealand
77 2025 Tier 325 Tier 327
10/16–18 Swustlicious
King of Prussia,, PA
216 2025 Tier 330 Tier 330
10/22–25 SASS- Spooky Albany Swing Spectacular
Latham, NY
165 2025 Tier 327 Tier 332
10/22–25 Swing City Chicago
Schaumburg, IL
467 2025 Tier 462 Tier 472
10/22–25 Halloween SwingThing
Costa Mesa, CA
413 2025 Tier 456 Tier 471
10/22–25 Augsburg Westie Station
Augsburg, Germany
321 2025 Tier 449 Tier 457
10/22–24 Swingside Invitational
Liège, Belgium
417 2025 Tier 464 Tier 589
10/28–11/2 Scandinavian Open WCS "SNOW"
Stockholm, Sweden
722 2025 Tier 5117 Tier 6130
10/29–11/1 Spooky Westie Weekend
Singapore, Singapore
109 2024 Tier 334 Tier 338
10/29–11/2 Warsaw Halloween Swing
Warsaw, Poland
525 2025 Tier 580 Tier 589

November 2026

11/5–8 Tampa Bay Classic
Tampa, FL
11/5–8 Mouintain Magic Dance Convention
Stateline, NV
139 2025 Tier 324 Tier 322
11/5–8 Simply Adelaide West Coast Swing
Adelaide, Australia
188 2025 Tier 338 Tier 339
11/5–9 Sea to Sky Seattle
Seattle, WA
421 2024 Tier 466 Tier 581
11/6–8 Westie Pink City
Blagnac, France
350 2025 Tier 445 Tier 470
11/6–8 Moscow Westie Fest Gala Edition
Moscow, Russia
184 2023 Tier 336 Tier 440
11/12–15 Westie's Angels
Saint-Maurice-de-beynost, France
365 2025 Tier 455 Tier 470
11/12–15 Midnight Madness WCS 2026
DFW Airport, TX
484 2025 Tier 465 Tier 585
11/12–15 Northeast Swing Classic
Warwick, RI
374 2025 Tier 443 Tier 474
11/13–15 Flowstate | West Coast Swing & Zouk Festival
London, United Kingdom
174 2025 Tier 332 Tier 450
11/13–15 Autumn Swing Challenge
Budapest, Hungary
413 2025 Tier 457 Tier 475
11/19–22 DC Swing eXperience (DCSX)
Herndon, VA
662 2025 Tier 589 Tier 587
11/19–22 Rocket City Swing
Huntsville, AL
212 2025 Tier 331 Tier 443
11/25–29 The Open Swing Dance Championships
Burbank, CA
517 2025 Tier 443 Tier 458
11/26–29 CASH Bash
Twinsburg, OH
177 2024 Tier 324 Tier 441

December 2026

12/3–6 Winter White
Oslo, Norway
405 2025 Tier 581 Tier 592
12/3–7 The After Party
Irvine, CA
509 2025 Tier 456 Tier 460
12/10–13 Berlin Swing Revolution
Berlin, Germany
183 2025 Tier 339 Tier 332
12/10–14 Santa Swing
Kraków, Poland
369 2025 Tier 474 Tier 587
12/18–20 Swiss Open WCS
Le Grand-Saconnex, Switzerland
Trial Event
12/28–1/3 UCWDC Country Dance World Championships
Dallas, TX
230 2025 Tier 336 Tier 453
12/30–1/3 Spotlight New Year's Celebration
Nashville, TN
602 2025 Tier 597 Tier 5109
12/30–1/3 Floorplay Swing Vacation
Orlando, FL
330 2025 Tier 455 Tier 462
12/30–1/3 New Year's Swing Fling
London, United Kingdom
215 2025 Tier 329 Tier 443
12/30–1/4 SwingVester
Wels, Austria
809 2025 Tier 6153 Tier 6159
12/31–1/4 Westie Gala
Upplands Väsby, Sweden
433 2025 Tier 468 Tier 474
12/31–1/3 Countdown Swing Boston
Mansfield, MA
308 2025 Tier 339 Tier 468

January 2027

1/5–10 BudaFest Open WCS Championships
Budapest, Hungary
1,033 2026 Tier 6158 Tier 6191
1/7–11 SwingCo
Portland, OR
371 2025 Tier 458 Tier 463
1/14–17 Cologne Calling WCS
Köln, Germany
Trial Event
1/14–17 Austin Swing Dance Championships
Round Rock, TX
376 2026 Tier 455 Tier 479
1/15–17 Avignon City Swing
Nimes, France
360 2026 Tier 455 Tier 584
1/15–17 Monterey Swing Fest
Monterey, CA
501 2026 Tier 471 Tier 584
1/21–24 Derby City Swing
Louisville, KY
234 2026 Tier 444 Tier 441
1/21–25 Swing Resolution
Edinburgh, United Kingdom
359 2026 Tier 451 Tier 464
1/22–24 Freedom Swing Dance Challenge
Wilmington, DE
302 2026 Tier 335 Tier 453
1/22–25 Swing In Paris
Paris, France
Trial Event

February 2027

2/4–7 Charlotte WestieFest
Charlotte, NC
569 2026 Tier 479 Tier 5108
2/4–7 Swingtzerland
Zurich, Switzerland
212 2026 Tier 449 Tier 337
2/4–7 Swing & Snow
Peterhof, Russia
298 2026 Tier 455 Tier 455
2/4–7 Waterloo Ontario Open West Coast Swing Championships
Kitchener, Canada
206 2026 Tier 322 Tier 448
2/5–7 Trinity Swing
Dundalk, Ireland
188 2026 Tier 331 Tier 445
2/9–14 Euro Dance Festival
Rust, Germany
412 2026 Tier 453 Tier 459
2/11–14 Capital Swing Convention
Sacramento, CA
526 2026 Tier 479 Tier 5105
2/18–21 Arousa Westie Fest
Sanxenxo, Spain
296 2026 Tier 338 Tier 454
2/18–22 Swing Crush
San Antonio, TX
353 2026 Tier 448 Tier 474
2/19–21 Prague Alchemy Swing
Prague, Czech Republic
448 2026 Tier 458 Tier 590
2/19–22 Valentine Swing
Gottröra, Sweden
281 2026 Tier 454 Tier 460
2/25–28 Southern Lights Swing
Hobart, Australia
183 2026 Tier 338 Tier 440
2/25–3/1 Paris Swing Classic
Paris, France
718 2026 Tier 593 Tier 5110
2/25–28 Winter Coast Swing
Leppävirta, Finland
291 2026 Tier 452 Tier 464
2/25–28 Rose City Swing
Tigard, OR
527 2026 Tier 479 Tier 590
2/26–28 New York Flow Festival
New York, NY
230 2026 Tier 336 Tier 338

March 2027

3/4–8 MADjam (Mid Atlantic Dance Jam)
Reston, VA
990 2026 Tier 5129 Tier 6155
3/5–7 Swingvasion
Wellington, New Zealand
134 2026 Tier 335 Tier 445
3/5–8 Norway Westie Fest
Stjørdal, Norway
Trial Event
3/11–14 Dutch Open Wcs
Venray, Netherlands
208 2026 Tier 448 Tier 334
3/11–14 Westie Spring Thing
Budapest, Hungary
433 2025 Tier 451 Tier 472
3/11–14 H-Town Throw Down 2027
Spring, TX
400 2026 Tier 460 Tier 472
3/11–15 5280 Westival
Greenwood Village, CO
210 2026 Tier 339 Tier 454
3/12–14 High Desert Dance Classic
Lancaster, CA
253 2026 Tier 336 Tier 445
3/18–22 West In Lyon
Dadrilly, France
530 2026 Tier 476 Tier 589
3/18–22 King Swing
Kraków, Poland
627 2026 Tier 5107 Tier 6157
3/18–21 The Chicago Classic
Schaumburg, IL
594 2026 Tier 599 Tier 596
3/19–22 The Boston Tea Party
Newton, MA
339 2026 Tier 452 Tier 589
3/25–28 Tulsa Spring Swing
Tulsa, OK
229 2026 Tier 446 Tier 446
3/25–29 UK West Coast Swing Championships
Sipson, United Kingdom
721 2026 Tier 5100 Tier 5119
3/25–28 Easter Swing
Bellevue, WA
685 2026 Tier 5113 Tier 5105

April 2027

4/8–11 KOREAN OPEN WCS CHAMPIONSHIPS
Incheon, South Korea
451 2026 Tier 448 Tier 461
4/8–11 Franconian Swing Project
Nürnberg, Germany
Trial Event
4/14–19 Slovenian Open
Ljubljana, Slovenia
395 2026 Tier 462 Tier 581
4/15–18 Asia WCS Open 2027
Singapore, Singapore
498 2026 Tier 582 Tier 590
4/16–18 WESTY NANTES
Nantes, France
437 2026 Tier 468 Tier 582
4/22–25 SwingIn Festival
Bonn, Germany
428 2026 Tier 472 Tier 472
4/22–25 Swing in Bloom
Ottawa, Canada
294 2026 Tier 337 Tier 472
4/22–25 HONEY FEST
Ufa, Russia
225 2026 Tier 339 Tier 443
4/22–25 Swingover 2027
Orlando, FL
575 2026 Tier 472 Tier 590
4/22–26 Detonation Dance
Manchester, United Kingdom
343 2026 Tier 446 Tier 458
4/29–5/2 Swingin' Into Spring
Windsor Locks, CT
186 2026 Tier 324 Tier 442
4/29–5/2 Calgary Dance Stampede
Calgary, Canada
222 2026 Tier 443 Tier 447
4/29–5/2 City of Angels WCS
Burbank, CA
421 2026 Tier 453 Tier 461
4/29–5/2 BeeMAD
Madrid, Spain
252 2026 Tier 336 Tier 451

May 2027

5/7–9 Westie Weekend
Silver Spring, MD
270 2026 Tier 446 Tier 461
5/13–17 Swingsation
Surfers Paradise, Australia
190 2026 Tier 444 Tier 453
5/13–16 SOswing
Ashland, OR
363 2026 Tier 465 Tier 580
5/20–23 Gateway Swing Classic
St. Louis, MO
304 2026 Tier 454 Tier 454
5/21–23 Nanaimo Dance Fusion
Nanaimo, Canada
177 2026 Tier 330 Tier 338
5/27–31 USA Grand National Dance Championships
Atlanta, GA
496 2026 Tier 466 Tier 476

June 2027

6/3–6 Michigan Classic
Romulus, MI
354 2026 Tier 447 Tier 462
6/3–7 Festival City Swing
Newcastle upon Tyne, United Kingdom
178 2026 Tier 327 Tier 335
6/10–13 D-TOWNSWING 2027
Duesseldorf, Germany
440 2026 Tier 460 Tier 468
6/10–14 Baltic Swing
Gdansk, Poland
453 2026 Tier 474 Tier 465
6/17–20 SWINGAPALOOZA
Baton Rouge, LA
292 2026 Tier 339 Tier 454
6/24–28 NeverlandSwing Dutch Swing Championships 2027
Amsterdam, Netherlands
255 2026 Tier 338 Tier 448
6/24–28 Liberty Swing Dance Championships
New Brunswick, NJ
602 2026 Tier 479 Tier 5114
6/24–27 BaroqueSwing
Ludwigsburg, Germany
299 2026 Tier 444 Tier 448
6/25–28 French Connection WCS
Annecy, France
393 2026 Tier 449 Tier 475

July 2027

7/1–4 Swing Valley
Bad Hofgaseitn, Austria
Trial Event
7/8–11 Carolina Summer Swing
Charlotte, NC
417 2025 Tier 457 Tier 587
7/15–18 Midwest Westie Fest
Overland Park, KS
420 2026 Tier 474 Tier 593

August 2027

8/12–15 Swing Fling 2027
Herdon, VA
573 2025 Tier 478 Tier 594

October 2027

10/7–10 Atlanta Swing Classic
Atlanta, GA
689 2025 Tier 599 Tier 5114
10/15–17 WCS Festival
Duesseldorf, Germany
358 2025 Tier 453 Tier 460

February 2028

2/29–3/5 Euro Dance Festival
Rust, Germany
412 2026 Tier 453 Tier 459

September 2028

9/28–10/1 Atlanta Swing Classic
Atlanta, GA
689 2025 Tier 599 Tier 5114

February 2029

2/13–18 Euro Dance Festival
Rust, Germany
412 2026 Tier 453 Tier 459

March 2030

3/5–10 Euro Dance Festival
Rust, Germany
412 2026 Tier 453 Tier 459