/*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 DRIVEWAY INSTALLATION

Durable Asphalt Driveways Start with Proper Site Preparation

Most driveway failures don’t begin with the asphalt, they begin underneath it. Poor drainage, unstable soils, sub-base and/or inadequate compaction. That’s why we evaluate the entire site before paving, helping you invest in a asphalt driveway that’s built to perform from the ground up not just look good on installation day.

A driveway plan grounded in the site's existing pavement, supporting base, drainage and garage access.

Compacting asphalt driveway
BASE FIRST

Prepare beneath the paving

Excavation, granular base and compaction respond to the site's conditions and intended vehicle use.

WATER AND GRADE

Plan surface drainage

Driveway slope and nearby inlets are reviewed before the final asphalt elevation is set.

PROPERTY CONNECTIONS

Fit the adjoining spaces

Garage entries, walks, curbs and landscape edges shape the contracted paving footprint.

FROM OLD SURFACE TO NEW DRIVEWAY

How an asphalt driveway is built

Replacement begins with the existing pavement and the ground below it. The five connected process records cover removal, excavation and base preparation, hot-mix placement, compaction and finishing, then inspection and cleanup. Drainage or adjoining construction is separately scoped when needed.

  1. Asphalt driveway demolition with heavy machinery breaking up old pavement.

    Step 1

    Asphalt Driveway Demolition

    Asphalt driveway demolition removes old, deteriorating surfaces to create a stable foundation for new paving, ensuring proper drainage and long-term durability.

    • Driveway removal
    • Pavement demolition
    • Site protection
  2. Premium quality subbase for asphalt driveway

    Step 2

    Asphalt Driveway Base Preparation

    Site preparation for asphalt driveways involves grading, compacting a stable subbase, and ensuring proper drainage for long-term durability.

    • Driveway base
    • Granular sub-base
    • Grade planning
  3. Asphalt placement with controlled spreading and vibratory rolling.

    Step 3

    Asphalt Driveway Placement

    Asphalt placement involves controlled spreading, temperature monitoring, and precise compaction to create a smooth, durable, and long-lasting driveway surface.

    • Hot-mix paving
    • Asphalt depth
    • Driveway placement
  4. Asphalt driveway finishing with compaction and edge sealing.

    Step 4

    Asphalt Driveway Finishing

    Asphalt driveway finishing involves compaction, edge sealing, and final surface treatments to ensure durability, smoothness, and long-lasting performance.

    • Roller compaction
    • Finished edges
    • Garage transition
  5. Asphalt truck unloads mix into paver during single-family driveway resurfacing.

    Step 5

    Asphalt Driveway Cleanup

    Final cleanup ensures a polished, professional finish by removing debris, inspecting the surface, and providing care recommendations for long-term driveway performance.

    • Final inspection
    • Site cleanup
    • Access guidance

Tony’s Homeowner Testimonial

Homeowner testimonial from Brookswood, Langley featuring an asphalt driveway, drainage improvements, retaining wall, and complete property transformation.

can you 

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 

THE GROUNDWORK MATTERS

Asphalt driveway construction for the property

VALROC Development Ltd. specializes in professionally installed asphalt driveways throughout Metro Vancouver and the Fraser Valley, built to withstand daily traffic and British Columbia’s West Coast climate. Whether for residential homes, strata communities, or multiplex developments, every asphalt driveway begins with proper excavation, grading, drainage, and a carefully prepared foundation, because long-term performance starts beneath the surface.

FOUNDATION

Prepare a stable base

Excavation, road-base depth and compaction respond to the existing ground and expected driveway traffic.

DRAINAGE

Set purposeful grades

Review where water goes at the garage, property edge and nearby low points before placing the surface.

EVERYDAY USE

Fit vehicles and access

Driveway width, turning space and transitions should suit the property rather than a generic footprint.

CONNECTED WORK

Coordinate adjoining trades

Walls, walks, curbs and drains can be evaluated alongside paving but stay separate unless listed in the proposal.

CHOOSE FOR THE SITE

Asphalt driveway options with a purpose

A new driveway needs an appropriate base, asphalt depth and grade as part of its defined design. Beyond that core scope, formed edges, more paved area or a separate drainage system may be useful at some properties. Each option should answer a real access, water or edge condition and appear in the written proposal.

Asphalt driveway installation support

Included in our service:

  • • Driveway layout and property access review

  • • Existing surface removal when required

  • • Excavation and subgrade preparation

  • • Premium aggregate base placement

  • • Compaction with asphalt paving equipment

  • • Drainage slopes away from structures

  • • Hot mix asphalt from established suppliers

  • • Paving and edge compaction

  • • Garage, sidewalk and roadway transitions

  • • Final walkthrough and care guidance

Additional driveway and drainage choices

Formed edges for asphalt driveways

Finished edge · Cleaner driveway lines
Asphalt driveway edges · Driveway curves · Edge control

Catch basin drainage for asphalt driveways

Drainage solution · Less surface water
Driveway catch basin · Asphalt drainage · Surface runoff

Asphalt driveway widening and extensions

Added space · More usable parking
Asphalt driveway widening · Parking space · Paving extension

A formed edge or wider footprint changes the driveway layout; a catch basin or French drain addresses a different water problem. Base strength and paving depth remain specifications to decide from site conditions and expected loads, not decorative upgrades.

Choose one coordinated driveway plan

View other available options

Catch-basin lid adjustment

An existing catch-basin rim may need to be set to the intended finished driveway grade. Adjustment is evaluated with the inlet and surrounding pavement, then written into scope if required. | Additional cost

French drainage near the driveway

A French drain may be considered when subsurface water affects a driveway edge or supporting base. It is a separate drainage decision, not a standard layer beneath every driveway. | Additional cost

Curb or retaining-edge integration

A concrete curb or retaining wall can address a specific grade change or pavement edge beside an asphalt driveway. Its design and construction are separately confirmed. | Additional cost

Tapered driveway transitions

A tapered asphalt connection can ease the change to a garage slab, walk or older pavement where elevations differ. The transition must also respect water flow. | Additional cost

Load-appropriate asphalt thickness

If an RV or other heavier vehicle will regularly use the driveway, asphalt depth and base support should be specified for that load. This is a design decision, not an automatic upsell. | Additional cost

BEYOND THE DRIVEWAY EDGE

Coordinate the driveway with the rest of the property

Many driveway projects uncover opportunities to improve the property beyond the asphalt itself. Because VALROC is a full-service construction company, we can complete related improvements during the same project, reducing the need to coordinate multiple contractors while creating a more complete long-term solution.

Walls, walks, curbs, drainage systems, rock pits and extra paving are separate services unless the written driveway proposal includes them.

  • VALROC Development Ltd. building a backyard detached garage or shop.

    Asphalt driveways for detached garage access

    • Garage driveway
    • Vehicle access
    • Garage entrance

    A detached garage needs a practical vehicle route that meets the door opening and finished floor elevation. Coordinating the asphalt driveway with the garage layout improves access, drainage and everyday use.

    Explore detached garage construction
  • A freshly stripped concrete retaining wall at a luxury residential build in Brookswood, Langley, BC, showing the VALROC excavator and crew preparing the drainage trench behind the wall.

    Retaining walls beside sloped asphalt driveways

    • Asphalt driveway retaining wall
    • Slope support
    • Driveway grade

    An engineered concrete retaining wall can support grade changes beside a sloped driveway and help create a wider, more stable driving area. Wall drainage and driveway grades should be planned as one site system.

    Explore engineered concrete retaining walls
  • 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 drains across asphalt driveway entrances

    • Asphalt driveway trench drain
    • Garage runoff
    • Entrance drainage

    A trench drain can capture runoff before it reaches a garage, building entrance or lower part of the property. Coordinating drain elevation with the asphalt surface helps water cross the driveway safely.

    Explore trench drain installation
  • Excavation site preparation with John Deere excavator and laser rotary measurement tool in use

    Site preparation for asphalt driveways

    • Driveway site preparation
    • Asphalt base
    • Subgrade stability

    A reliable asphalt driveway depends on suitable subgrade, drainage and compacted base material. Site preparation helps establish the driveway footprint, remove weak material and shape the planned elevations.

    Explore site preparation services
  • Man working on staircase construction in Vancouver, BC, residential project, building new wooden steps.

    Concrete steps between driveways and entrances

    • Driveway entrance steps
    • Elevation access
    • Concrete landing

    Where an asphalt driveway sits below a doorway or walkway, concrete steps can create a comfortable transition between elevations. Planning the landing, drainage and approach together improves access to the home.

    Explore concrete steps and staircases

DOCUMENTED EXPERIENCE

Asphalt driveway experience across Metro Vancouver

VALROC installs and renews asphalt driveways for homes, strata properties and businesses across Metro Vancouver. Our experience helps coordinate the driveway base, grading, drainage, edges and connections to garages, roads and walkways. From our Langley construction yard, we regularly serve Brookswood, Willoughby, Walnut Grove, Cloverdale, South Surrey and White Rock, along with residential and strata properties across Burnaby and Richmond.

A SCOPE YOU CAN REVIEW

Request an asphalt driveway estimate

An onsite driveway assessment can confirm the existing surface, grades, vehicle use and adjoining structures before pricing. The proposal should distinguish base preparation and asphalt placement from any separately requested drainage, retaining or widening work.

  • Share the property context

    Send the address, driveway photos and any garage, drainage or access concern.

  • Review the site

    Walk the footprint, elevations, existing paving and intended vehicle use.

  • Compare the written scope

    See the defined paving work and any separate drainage, edge or extension items.

  • What affects an asphalt driveway quote?

    The driveway footprint, removal, excavation, base condition, material depth, drainage and grade, equipment access, edges and transitions determine the project-specific work. A photo helps start the conversation but cannot establish hidden ground conditions.

    What affects an asphalt driveway quote?

    The driveway footprint, removal, excavation, base condition, material depth, drainage and grade, equipment access, edges and transitions determine the project-specific work. A photo helps start the conversation but cannot establish hidden ground conditions.

    Request Your Free On-site Estimate

    BEFORE THE PAVER ARRIVES

    Check the driveway, grades and ground

    Before recommending an asphalt driveway, we take the time to understand your property.

  • 01

    Walk the existing driveway

    Review cracking, settlement, soft spots, existing repairs and the usable paving footprint.

  • 02

    Check elevations and water

    Measure the garage threshold, adjoining walks, low points and where surface water can safely travel.

  • 03

    Define the base and layout

    Agree on excavation, granular support, vehicle use, edges and access for paving equipment.

  • 04

    Confirm the written scope

    Separate the driveway installation from any drainage, wall, curb or additional paving work.

  • What changes the paving plan?

    Existing soil and road-base condition, garage elevations, nearby roots, water movement, retaining structures and future vehicle use all affect the plan. Conditions exposed during removal may require a scope discussion before new material is placed.

    What changes the paving plan?

    Existing soil and road-base condition, garage elevations, nearby roots, water movement, retaining structures and future vehicle use all affect the plan. Conditions exposed during removal may require a scope discussion before new material is placed.

    PAVING QUESTIONS ANSWERED

    Asphalt driveway questions

    The connected questions cover replacement versus repair, climate, maintenance and when a newly paved driveway can be used. Actual recommendations and access timing depend on the inspected property, weather and completed work.

    Do I need to replace the whole driveway, or can this just be repaired?

    Not all damaged driveways require full replacement. VALROC begins each project with an on-site inspection to determine if patching is possible. If the subbase is still intact, a localized repair can extend the surface’s lifespan. If deeper issues like erosion or drainage problems are found, we’ll advise on partial reconstruction to avoid recurring costs.

    Do I need to replace the whole driveway, or can this just be repaired?

    Not all damaged driveways require full replacement. VALROC begins each project with an on-site inspection to determine if patching is possible. If the subbase is still intact, a localized repair can extend the surface’s lifespan. If deeper issues like erosion or drainage problems are found, we’ll advise on partial reconstruction to avoid recurring costs.

    How long does an asphalt driveway last in British Columbia’s climate?

    In British Columbia, a professionally installed asphalt driveway typically lasts between 15 and 25 years.

    • Frequent rainfall and wet winters increase surface wear.
    • Freeze-thaw cycles contribute to cracking if moisture penetrates the asphalt.
    • Traffic volume and vehicle weight influence long-term durability.
    • Regular maintenance significantly extends lifespan.

    Proper drainage, sealcoating, and timely repairs play a major role in maximizing longevity.

    How long does an asphalt driveway last in British Columbia’s climate?

    In British Columbia, a professionally installed asphalt driveway typically lasts between 15 and 25 years.

    • Frequent rainfall and wet winters increase surface wear.
    • Freeze-thaw cycles contribute to cracking if moisture penetrates the asphalt.
    • Traffic volume and vehicle weight influence long-term durability.
    • Regular maintenance significantly extends lifespan.

    Proper drainage, sealcoating, and timely repairs play a major role in maximizing longevity.

    What type of maintenance does an asphalt driveway need in BC?

    Routine maintenance protects asphalt from moisture damage, cracking, and premature surface failure.

    • Sealcoating every 2 to 3 years to limit water penetration.
    • Filling cracks early to prevent moisture from reaching the base layer.
    • Maintaining proper drainage to avoid standing water.
    • Varying parking positions to prevent surface depressions.

    Consistent maintenance helps asphalt withstand BC’s climate and reduces long-term repair costs.

    What type of maintenance does an asphalt driveway need in BC?

    Routine maintenance protects asphalt from moisture damage, cracking, and premature surface failure.

    • Sealcoating every 2 to 3 years to limit water penetration.
    • Filling cracks early to prevent moisture from reaching the base layer.
    • Maintaining proper drainage to avoid standing water.
    • Varying parking positions to prevent surface depressions.

    Consistent maintenance helps asphalt withstand BC’s climate and reduces long-term repair costs.

    How soon after installation can I use my new asphalt driveway?

    New asphalt needs time to cool, set, and gain strength before regular use.

    • Walking is typically safe after 24 to 48 hours.
    • Driving is usually safe after 5 to 7 days, depending on weather conditions.
    • Asphalt continues curing for several months after installation.
    • Sharp turns, heavy vehicles, and stationary loads should be avoided during the first few weeks.

    Allowing proper curing time helps prevent dents, scuffing, and surface deformation.

    How soon after installation can I use my new asphalt driveway?

    New asphalt needs time to cool, set, and gain strength before regular use.

    • Walking is typically safe after 24 to 48 hours.
    • Driving is usually safe after 5 to 7 days, depending on weather conditions.
    • Asphalt continues curing for several months after installation.
    • Sharp turns, heavy vehicles, and stationary loads should be avoided during the first few weeks.

    Allowing proper curing time helps prevent dents, scuffing, and surface deformation.

    How does rain and snow impact an asphalt driveway in BC?

    British Columbia’s wet climate and freeze-thaw cycles can affect asphalt over time if not properly managed.

    • Water penetration can weaken the asphalt base and lead to cracking.
    • Freeze-thaw cycles cause expansion and contraction that accelerates surface wear.
    • Proper drainage helps prevent standing water and long-term damage.
    • Sealcoating provides a protective barrier against moisture infiltration.
    • Using plastic shovels, rubber-edged plows, and sand instead of salt reduces surface damage.

    With the right drainage and maintenance practices, asphalt performs well in BC’s climate.

    How does rain and snow impact an asphalt driveway in BC?

    British Columbia’s wet climate and freeze-thaw cycles can affect asphalt over time if not properly managed.

    • Water penetration can weaken the asphalt base and lead to cracking.
    • Freeze-thaw cycles cause expansion and contraction that accelerates surface wear.
    • Proper drainage helps prevent standing water and long-term damage.
    • Sealcoating provides a protective barrier against moisture infiltration.
    • Using plastic shovels, rubber-edged plows, and sand instead of salt reduces surface damage.

    With the right drainage and maintenance practices, asphalt performs well in BC’s climate.

    METRO VANCOUVER DRIVEWAYS

    Driveway installation across the service area

    From our Langley construction yard, VALROC installs asphalt driveways across Brookswood, Willoughby, Walnut Grove and surrounding communities. Excavation, aggregate base, compaction, drainage grades and hot mix asphalt delivery are coordinated around garage elevations, roadway connections and everyday vehicle access.

    Driveways in Surrey and Delta may serve longer approaches or larger residential properties, while Burnaby and White Rock projects can require careful grade transitions and tighter equipment access. Each driveway is shaped around the property, expected traffic and surrounding surfaces.