/*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 .dwc-nest-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;
}

body: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 .dwc-nest-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;
}

:where(.brx-header-left, .brx-header-right)[data-builder-mode]:not(:has(.dwc-sidebar)) .dwc-nest-menu-top::before {
    content: 'this space is the back text bar';
    padding: 1rem;
    background-color: gray;
    color: white;
    width: 100%;
    text-transform: uppercase;
    font-size: 12px;
    font-weight: bold;
    letter-spacing: 1px;
}
 
/* 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();

ASPHALT PATCH & REPAIR

Reliable Asphalt Patch & Repair Services for Metro Vancouver.

Asphalt patching and repairs that address sub-base failure, water damage, and traffic stress, not just surface cracks.

Onsite diagnosis, a defined repair boundary and a written scope for the pavement actually affected.

asphalt patch and repair services.
BELOW THE SURFACE

Find the cause first

We review cracking, settlement, traffic and water before deciding how far a patch should extend.

STRATA AND BUSINESS ACCESS

Plan around active sites

Repair areas, pedestrian routes and vehicle access can be coordinated with a property contact.

IN-HOUSE EQUIPMENT

Prepare, pave and compact

VALROC can bring saws, grading tools, compactors and rollers suited to the defined patch scope.

FROM FAILED EDGE TO FINISHED PATCH

How a hot-mix asphalt repair is built

Most scoped repairs use hot-mix asphalt after the failed area is removed and the supporting base is checked. Portable saws and rotary lasers can help define cuts and grades; double-drum rollers or walk-behind compactors are selected to suit the patch size and access. Base work, asphalt placement and compaction follow the site assessment; catch-basin or drainage work is included only when written into the proposal.

  1. Shared strata lot asphalt repair

    Step 1

    Asphalt Patch Demolition

    Asphalt patch demolition removes failed surface areas by cutting back to solid pavement, ensuring proper bonding and long-term repair strength for roads, parking lots, and drive aisles.

    • Asphalt sawcut
    • Failed pavement
    • Repair boundary
  2. underground parkade patch and repair services

    Step 2

    Asphalt Patch & Repair Site Prep

    Site preparation for asphalt patching includes removing soft base material, compacting a solid subgrade, and setting edges to ensure long-term durability and drainage performance.

    • Road base
    • Subgrade repair
    • Base compaction
  3. repairing trench drain asphalt connection

    Step 3

    Asphalt Patch Placement

    Asphalt placement for patch repairs involves hot mix installation, edge bonding, and full compaction to ensure seamless integration with existing surfaces and long-term durability.

    • Hot-mix patch
    • Asphalt placement
    • Patch depth
  4. hot asphalt placement for sidewalk install

    Step 4

    Asphalt Patch Finishing

    Final finishing ensures asphalt patches are smooth, level, and sealed at the edges, restoring surface flow, visual consistency, and structural performance across repaired areas.

    • Roller compaction
    • Surface grade
    • Patch edges
  5. community roadway repair asphalt transition

    Step 5

    Patch & Repair Cleanup

    Final cleanup removes debris, restores surrounding areas, and ensures the patched asphalt surface is clean, sealed, and ready for safe, immediate use.

    • Site cleanup
    • Access reopening
    • Safety handoff

Asphalt Paving, Roadways, Driveways, Repair...

This video showcases VALROC’s asphalt paving projects across Metro Vancouver, including driveways, strata roadways, and commercial parking lots. Watch our in-house crews and equipment deliver structured, durable asphalt installations built for long-term performance.

can you 

DIAGNOSE BEFORE YOU PATCH

What a localized asphalt repair can solve

When asphalt begins to crack, sink, or break apart, it’s not just a surface issue it usually signals deeper concerns below the pavement. Problems like water pooling, shifting ground, or repetitive traffic pressure (especially from heavier vehicles) can wear down the sub-base. This weakens the asphalt above, making your surface unsafe and unsightly. Proper Asphalt Patch & Repair can help address these concerns effectively.

FAILURE SIGNS

Cracking and settlement

Alligator cracking, potholes or a sinking patch can indicate weak support or trapped water. We assess the affected area before selecting removal depth and limits.

SAFER ROUTES

Address a trip concern

Strata pathways, parking areas and business entries may need a repair plan that isolates work while maintaining a practical pedestrian route.

TRAFFIC LOADS

Plan for turning vehicles

Repeated slow turns from garbage trucks, deliveries or ordinary vehicles can stress pavement near loading and collection points; traffic use matters when defining the repair.

SITE OPERATIONS

Limit avoidable disruption

A point person can coordinate notices, parked vehicles and the work zone. Phasing or after-hours scheduling can be discussed where feasible.

ONLY WHEN THE SITE NEEDS IT

Choose connected repairs for the actual failure

A standard patch does not automatically include a new catch basin, drainage system or curb. If standing water, a settled inlet or a difficult pavement transition contributes to failure, those corrections can be assessed and separately scoped alongside the asphalt work.

Asphalt patch repair support from start to handoff

Included in our service:

  • • Onsite review of failed asphalt areas

  • • Saw cutting and removal of damaged asphalt

  • • Subgrade review and localized base repair

  • • Compaction with equipment suited to the patch

  • • Hot mix asphalt ordered for the repair

  • • Clean joints tied into the existing pavement

  • • Traffic access coordinated for homes and businesses

  • • Final walkthrough of the completed patch work

Site-specific repair options

Catch basin rim adjustments with asphalt patching

Drainage detail · Smooth inlet transition
Catch basin adjustment · Asphalt patching · Inlet drainage

Drainage correction beside asphalt repairs

Water control · Reduced pooling at repair
Asphalt repair drainage · Standing water · Surface runoff

Tapered transitions for asphalt patching

Connection detail · Smoother pavement transition
Asphalt patch transition · Pavement edge · Grade connection

The patch dimensions and aggregate base follow what the onsite assessment reveals. Raising a catch-basin rim, altering drainage or adding a tapered transition is proposed separately if those items are needed; they are not hidden in a generic patch allowance.

Treat the cause, not only the opening

AROUND THE REPAIR AREA

Coordinate pavement, drainage and edges

An asphalt patch can meet a catch basin, concrete curb, walkway, driveway, loading zone or retaining edge. If one of those interfaces is contributing to failure, consider it in the site plan rather than assuming a surface patch alone resolves it.

Concrete curbing, new drainage systems, retaining walls and broader resurfacing are separate services unless the proposal expressly includes them.

  • Hot asphalt patching in Vancouver parking lot - professional asphalt repair services.

    Asphalt patching within parking lots

    • Parking lot patching
    • Asphalt repair
    • Pavement restoration

    Localized failures, utility cuts and settled areas can often be repaired without replacing the entire parking lot. Strategic patching restores problem areas while supporting safer traffic and parking use.

    Explore asphalt parking lot installation
  • Valroc crew performing full asphalt roadway demolition in a strata complex, removing failed pavement and preparing for new subbase and catch basin elevation adjustments.

    Asphalt roadway patch and repair

    • Roadway patching
    • Private road repair
    • Asphalt restoration

    Private roads can develop isolated cracks, depressions and damaged utility cuts. Targeted asphalt repair can restore ride quality and slow further deterioration around the affected area.

    Explore asphalt roadway installation
  • Compacting asphalt driveway

    Asphalt driveway patching and transitions

    • Driveway asphalt repair
    • Pavement patch
    • Smooth transition

    A damaged driveway area or rough connection to newer paving may not require full replacement. Patching can rebuild the failed section and create a smoother transition for vehicles.

    Explore asphalt driveway installation
  • Catch basin being installed on subbase ground while contractor holds level and excavator helps lift in the catch basin

    Catch basin adjustments during asphalt repairs

    • Catch basin adjustment
    • Asphalt drainage repair
    • Rim elevation

    Sunken or raised catch basin rims can interrupt drainage and create rough vehicle transitions. Adjusting the basin during asphalt repair helps align the inlet with the restored pavement surface.

    Explore catch basin installation
  • Aaron team member installing a trench drain in front of a garage diverting water that has been damaging the concrete and undermining the foundation

    Trench drain repairs connected to asphalt patches

    • Trench drain asphalt repair
    • Channel edge
    • Pavement connection

    Failed pavement beside a trench drain may indicate settlement, edge damage or drainage concerns. Repairing the asphalt connection can improve the approach while protecting the drain channel.

    Explore trench drain installation

DOCUMENTED EXPERIENCE

Asphalt repair experience across Metro Vancouver

VALROC repairs failed and deteriorated asphalt for strata, business and residential properties across Metro Vancouver. Our experience helps us assess the damaged area, surrounding pavement, drainage and site access before recommending a practical repair approach. From our Langley construction yard, crews regularly support asphalt repair work in Surrey, Burnaby, Richmond and strata communities throughout Langley, including Willoughby and Walnut Grove.

CLEAR SCOPE BEFORE PRICING

Request an onsite asphalt repair estimate

Within VALROC’s service catchment, an onsite estimate can assess the pavement and the work required. A photo is welcome with the request, but a phone description alone cannot establish the hidden base condition or the correct repair boundary.

  • Send the location and photos

    Show the failed surface, any standing water and the nearby inlet or traffic route; identify a property contact.

  • Walk the site together

    Review patch extent, base concerns, access, pedestrian safety, traffic and practical timing.

  • Receive a defined proposal

    See what is included, which connected repairs are separate, and how work-zone communication will be handled.

  • What affects the cost of asphalt patching?

    Patch size and depth, how much failed base must be removed, aggregate replacement, drainage or catch-basin adjustments, hot-mix access, compaction, traffic control and scheduling all affect the scope. A site visit is needed before giving a project-specific price.

    What affects the cost of asphalt patching?

    Patch size and depth, how much failed base must be removed, aggregate replacement, drainage or catch-basin adjustments, hot-mix access, compaction, traffic control and scheduling all affect the scope. A site visit is needed before giving a project-specific price.

    Request Your Free On-site Estimate

    LOOK UNDER THE FAILURE

    An onsite inspection defines the repair

    Photos help show where the surface is failing, but they cannot establish the condition of the road base. An onsite visit checks alligator cracking, settlement, water, turning traffic, catch-basin grades and the sound edge beyond visible damage before a patch boundary is set.

  • 01

    Document the concern

    Send photos and note standing water, a trip point, heavy-vehicle route or recurring failure before the visit.

  • 02

    Inspect beyond the visible crack

    Check pavement edges, base support, traffic patterns and adjacent catch basins; mark where damaged material should be removed.

  • 03

    Plan grades and access

    Review the direction of surface water, the intended inlet and safe vehicle and pedestrian routes during the work.

  • 04

    Write a repair scope

    Set out patch dimensions, base work, hot-mix placement, compaction and any separate drainage or inlet adjustment.

  • What photos cannot tell us

    A photo may show alligator cracking, a depression or pooling water. It cannot confirm hidden moisture, soft subgrade, base depth or the right cut line. Those decisions need an onsite inspection, and some conditions only become visible after removal.

    What photos cannot tell us

    A photo may show alligator cracking, a depression or pooling water. It cannot confirm hidden moisture, soft subgrade, base depth or the right cut line. Those decisions need an onsite inspection, and some conditions only become visible after removal.

    REPAIR DECISIONS EXPLAINED

    Questions about asphalt patching and repair

    The existing questions cover settlement, patch appearance, multiple strata repairs, operational access, heavy vehicles and when broader resurfacing may be preferable. The answers should be checked against a site visit rather than treated as an unseen diagnosis.

    Why is my driveway sinking in certain spots?

    Sinking typically occurs when the sub-base beneath the asphalt has failed.

    • Water pooling that erodes the base material.
    • Poor compaction during the original installation.

    We remove failed materials, repack and stabilize the base, correct drainage, and resurface with new asphalt.

    Why is my driveway sinking in certain spots?

    Sinking typically occurs when the sub-base beneath the asphalt has failed.

    • Water pooling that erodes the base material.
    • Poor compaction during the original installation.

    We remove failed materials, repack and stabilize the base, correct drainage, and resurface with new asphalt.

    Will you match the repaired asphalt to the existing surface?

    Exact colour matching is difficult due to natural asphalt ageing.

    • Careful blending techniques are used to reduce visual contrast.
    • Optional sealing across larger areas can help create a more uniform appearance.

    Our focus is on performance first, with appearance handled as cleanly as possible.

    Will you match the repaired asphalt to the existing surface?

    Exact colour matching is difficult due to natural asphalt ageing.

    • Careful blending techniques are used to reduce visual contrast.
    • Optional sealing across larger areas can help create a more uniform appearance.

    Our focus is on performance first, with appearance handled as cleanly as possible.

    Can you handle multiple problem areas across our complex?

    Yes. VALROC has experience managing large-scale patching across strata properties.

    • Assessment of all identified concern areas.
    • Prioritization based on severity and risk.
    • Phased repairs to minimize disruption to residents and access routes.

    This approach helps address multiple issues efficiently without overwhelming the site.

    Can you handle multiple problem areas across our complex?

    Yes. VALROC has experience managing large-scale patching across strata properties.

    • Assessment of all identified concern areas.
    • Prioritization based on severity and risk.
    • Phased repairs to minimize disruption to residents and access routes.

    This approach helps address multiple issues efficiently without overwhelming the site.

    Can you work around tenant parking schedules or provide a phased approach?

    Yes. Strata and multi-tenant properties require careful coordination to maintain access.

    • Work scheduled during off-peak hours where possible.
    • Clear coordination with property managers and advance resident notices.
    • Sectioned repairs to keep portions of parking accessible.

    Our goal is to complete the work efficiently while minimizing disruption to residents and tenants.

    Can you work around tenant parking schedules or provide a phased approach?

    Yes. Strata and multi-tenant properties require careful coordination to maintain access.

    • Work scheduled during off-peak hours where possible.
    • Clear coordination with property managers and advance resident notices.
    • Sectioned repairs to keep portions of parking accessible.

    Our goal is to complete the work efficiently while minimizing disruption to residents and tenants.

     Do you offer strata maintenance packages for recurring upkeep?

    Yes. We provide customized asphalt maintenance programs designed for strata communities, including routine inspections, crack sealing, surface treatments, and long-term planning for budgeting and safety improvements.

     Do you offer strata maintenance packages for recurring upkeep?

    Yes. We provide customized asphalt maintenance programs designed for strata communities, including routine inspections, crack sealing, surface treatments, and long-term planning for budgeting and safety improvements.

    Can the damaged sections in our parking lot be repaired without disrupting operations?

    In most cases, yes. Many repairs can be completed without shutting down operations.

    • After-hours or early-morning work options.
    • Phased repairs that isolate only the affected areas.
    • Crews experienced working safely in active environments.

    This allows businesses and residents to continue using the site with minimal interruption.

    Can the damaged sections in our parking lot be repaired without disrupting operations?

    In most cases, yes. Many repairs can be completed without shutting down operations.

    • After-hours or early-morning work options.
    • Phased repairs that isolate only the affected areas.
    • Crews experienced working safely in active environments.

    This allows businesses and residents to continue using the site with minimal interruption.

    We’ve got heavy vehicles on-site, will the patch hold up?

    Areas that see heavy vehicles require a different approach than standard patching.

    • Use of industrial-grade sub-base materials.
    • Hot-mix asphalt designed for higher load tolerance.
    • Increased asphalt thickness or reinforcement when required.

    Each repair is designed around actual site usage, not a one-size-fits-all solution.

    We’ve got heavy vehicles on-site, will the patch hold up?

    Areas that see heavy vehicles require a different approach than standard patching.

    • Use of industrial-grade sub-base materials.
    • Hot-mix asphalt designed for higher load tolerance.
    • Increased asphalt thickness or reinforcement when required.

    Each repair is designed around actual site usage, not a one-size-fits-all solution.

     Is it better to resurface the area or patch it now and deal with the rest later?

    This depends on the extent of the damage.

    • If problems are isolated, patching may be more cost-effective.
    • If deterioration is widespread, resurfacing may save more in the long run.

    We walk you through both options and provide transparent cost comparisons so you can make an informed decision.

     Is it better to resurface the area or patch it now and deal with the rest later?

    This depends on the extent of the damage.

    • If problems are isolated, patching may be more cost-effective.
    • If deterioration is widespread, resurfacing may save more in the long run.

    We walk you through both options and provide transparent cost comparisons so you can make an informed decision.

    What’s causing the same spot to keep breaking down, and how do we fix it for good?

    Repeat failure in the same area is usually caused by underlying issues.

    • Poor sub-base conditions.
    • Drainage problems allowing water to weaken the base.

    We remove the problem area, rebuild it from the ground up with proper compaction and grading, and improve drainage if needed to provide a lasting solution.

    What’s causing the same spot to keep breaking down, and how do we fix it for good?

    Repeat failure in the same area is usually caused by underlying issues.

    • Poor sub-base conditions.
    • Drainage problems allowing water to weaken the base.

    We remove the problem area, rebuild it from the ground up with proper compaction and grading, and improve drainage if needed to provide a lasting solution.

    METRO VANCOUVER

    Asphalt repairs for active properties

    From our Langley construction yard, VALROC dispatches asphalt repair crews, compactors and material support for failed pavement across residential, strata and business properties. Patch work in Langley and Surrey often involves driveway transitions, parking areas, loading zones and localized settlement where access must remain practical.

    In Burnaby and Delta, asphalt patch planning may also respond to constrained work areas, drainage edges, traffic routes and connections to older pavement. Each repair is reviewed for removal depth, base condition, compaction and a clean tie into the surrounding asphalt.