/* ========================================
MOBILE ONLY STYLES
add and modify varibales for mobile breakpoint
========================================= */
html.dwc-mobile {
--mobile-menu-width: min(450px, 100%);
--menu-item-font-size: 18px;
--dropdown-item-font-size: var(--menu-item-font-size);
--back-text-font-size: 16px;
--menu-item-hover-border-bg: initial;
}
/* =======================================
GENERAL NAV STYLES
========================================= */
:root {
/* ========================
GENERAL COLORS/BACKGROUND
======================== */
--primary-clr: orangered;
--header-bg: white;
--dropdown-content-bg: white;
--mobile-menu-bg: white;
--mobile-menu-topbar-bg: white;
/* ===============================
GENERAL WIDTH | HEIGHT | SPACINGS
================================== */
--mobile-menu-width: min(300px, 100%); /* mobile & offcanvas */
--multilevel-dropdown-width: 200px;
--dropdown-content-gap: 1px; /* header -> dropdown gap, add unit (e.g. 0px) */
--header-min-height: 60px;
--fullscreen-mobile-menu-top-height: 60px;
--top-offset: 40px; /* when nav is below header */
--dropdown-content-default-width: 1080px; /* preview width & default width for dropdown content */
/* ==============================================
GENERAL BORDERS | SHADOWS | OVERLAY BACKDROP
================================================= */
/* dropdown content */
--dropdown-content-border-radius: 0px;
--dropdown-content-shadow: 0px 5px 15px -10px rgb(0 0 0 / 0%);
--dropdown-content-border-size: 1px; /* at least 1px */
--dropdown-content-border-color: var(--dropdown-content-bg);
/* overlay backdrop */
--nav-overlay-backdrop-blur: 0px;
--nav-overlay-backdrop-clr: rgba(0, 0, 0, 0.3);
/* sidebar nav */
--sidebar-shadow: 0px 0px 2px rgba(0, 0, 0, 0.7);
/* radius for special mobile style on overlay header*/
--mobile-menu-radius: var(--overlay-header-radius);
/* mobile & offcanvas */
--slide-out-speed: 1.3;
/* ===============================
MENU TOGGLE - Hamburger
(additional settings at line 202)
================================== */
--menu-toggle-clr: var(--menu-item-clr);
--menu-close-toggle-clr: var(--menu-item-clr);
--menu-toggle-hover-clr: var(--menu-item-hover-clr);
/* ========================
MENU ITEMS
======================== */
/* DEFAULT STATE */
--menu-item-clr: #000;
--menu-item-font-size: 14px;
--menu-item-font-weight: 500;
--menu-item-bg: initial;
/* HOVER STATE */
--menu-item-hover-clr: var(--primary-clr);
--menu-item-hover-bg: initial;
--menu-item-hover-border-bg: var(--menu-item-active-border-bg);
--menu-item-hover-border-height: var(--menu-item-active-border-height);
/* ACTIVE STATE
(to exclude a link from this style,
add .dwc-exclude to its container)*/
--menu-item-active-clr: var(--menu-item-hover-clr);
--menu-item-active-bg: initial;
--menu-item-active-border-bg: var(--primary-clr);
--menu-item-active-border-height: 2px;
/* PADDING | GAP */
--menu-item-inline-padding: 1.1rem;
--menu-item-block-padding: 1rem;
--menu-items-gap: 0;
/* BORDERS | RADIUS */
--menu-item-border: 1px solid rgba(0, 0, 0, 0.1);
--menu-item-radius: 0;
/* CHEVRON (dropdown arrow) */
--chevron-size: 14px;
--chevron-clr: var(--menu-item-clr);
--chevron-hover-clr: var(--menu-item-hover-clr);
/* ========================
MULTILEVEL DROPDOWN LINKS
======================== */
/* DEFAULT STATE */
--dropdown-item-clr: var(--menu-item-clr);
--dropdown-item-font-size: var(--menu-item-font-size);
--dropdown-item-bg: initial;
--dropdown-indent-bg: rgb(0 0 0 / 5%);
--dropdown-heading-clr: var(--primary-clr);
/* HOVER STATE */
--dropdown-item-hover-clr: var(--menu-item-hover-clr);
--dropdown-item-hover-bg: white;
/* EXPANDED STATE (PARENT) - mobile */
/* when [data-submenu-reveal="expand"] */
--dropdown-expanded-clr: white;
--dropdown-expanded-bg: black;
/* PADDING | GAP | INDENT */
--dropdown-item-inline-padding: var(--menu-item-inline-padding);
--dropdown-item-block-padding: var(--menu-item-block-padding);
--dropdown-indent: 0.6rem;
--dropdown-indent-item-pad-offset: 0.5;
--dropdown-indent-line: solid 1px rgb(0 0 0 / 25%);
/* OTHERS */
--dropdown-inactive-overlay: rgb(0 0 0 / 10%);
/* ========================
MENU CTA BUTTON (ALL BUTTONS)
======================== */
/* all cta buttons max width on mobile */
--cta-width: 100%;
/* gap offset between 2 or 3 cta buttons on mobile/offcanvas/sidebar*/
--cta-gap-offset: 0;
/* gap between breakout cta and menu toggle on mobile */
--cta-breakout-gap: 20px;
/* ========================
MENU CTA BUTTON (LAST BUTTON)
======================== */
/* DEFAULT STATE */
--menu-cta-clr: white;
--menu-cta-bg: black;
--menu-cta-inline-padding: calc(var(--menu-item-inline-padding) * 1.3);
--menu-cta-block-padding: var(--menu-item-block-padding);
--menu-cta-border: none;
--menu-cta-radius: 0em;
/* HOVER STATE */
--menu-cta-hover-clr: white;
--menu-cta-hover-bg: var(--primary-clr);
/* ========================
MENU CTA BUTTON (SECOND BUTTON)
======================== */
/* DEFAULT STATE */
--menu-cta-2-clr: white;
--menu-cta-2-bg: black;
--menu-cta-2-inline-padding: var(--menu-cta-inline-padding);
--menu-cta-2-block-padding: var(--menu-cta-block-padding);
--menu-cta-2-border: var(--menu-cta-border);
--menu-cta-2-radius: var(--menu-cta-radius);
/* HOVER STATE */
--menu-cta-2-hover-clr: white;
--menu-cta-2-hover-bg: var(--primary-clr);
/* ========================
MENU CTA BUTTON (THIRD BUTTON)
======================== */
/* DEFAULT STATE */
--menu-cta-3-clr: white;
--menu-cta-3-bg: black;
--menu-cta-3-inline-padding: var(--menu-cta-inline-padding);
--menu-cta-3-block-padding: var(--menu-cta-block-padding);
--menu-cta-3-border: var(--menu-cta-border);
--menu-cta-3-radius: var(--menu-cta-radius);
/* HOVER STATE */
--menu-cta-3-hover-clr: white;
--menu-cta-3-hover-bg: var(--primary-clr);
/* ========================
MENU TOGGLE - Hamburger additional settings
======================== */
--open-icon-size: 40px;
--open-icon-line-height: 4px;
--icon-line-gap: 0.7em; /* gap between the lines*/
--open-icon-line-variance: 9px; /* line width variation, e.g. 0 = same width*/
--open-icon-align: 0; /* 0 = right | auto = left */
--open-icon-horizontal-offset: 0px; /* nudge icon left or right from edge of screen*/
--open-icon-close-offset: 1.2; /* nudge icon left or right when close icon is active */
/* ======================================
ADAPTIVE HEIGHT/ STRIPE BG COLOR/BORDER
========================================= */
--adaptive-height-bg: #fff;
--adaptive-height-border: 1px solid #fff;
--adaptive-height-shadow: 0 0 30px rgb(39 50 59 / 10%);
/* ========================
STRIPE - when [data-optimize-stripe="true"]
======================== */
--stripe-border-radius: 10px;
/* ========================
MOBILE/OFFCANVAS MENU
======================== */
--mobile-menu-ttf: cubic-bezier(0.8, 0.07, 0.2, 0.95);
/* Transition timing function */
/* =================
OVERLAY HEADER
================== */
--overlay-header-width: 1400px;
--overlay-header-inset: 1rem;
--overlay-header-bg: rgb(255 255 255 / 100%);
--overlay-header-bg-active: rgb(255 255 255 / 100%);
--overlay-header-blur: 10px;
--overlay-header-radius: 1rem;
--overlay-header-shadow: 0px 2px 20px rgb(0 0 0 / 20%);
--overlay-offset-padding: clamp(5rem, 1.875rem + 12.5vw, 11.25rem);
/* ========================
BACK TEXT
======================== */
--back-text-clr: var(--menu-item-clr);
--back-text-font-size: 12px;
--back-text-font-weight: 600;
--back-text-transform: uppercase;
--back-text-bg: var(--mobile-menu-topbar-bg);
/* ========================
SIDEBAR NAV - OVERLAY MODE
======================== */
--overlay-sidebar-radius: 1rem;
--overlay-sidebar-bg: rgb(255 255 255 / 80%);
--overlay-sidebar-shadow: 0 0 30px rgb(39 50 59 / 10%);
--overlay-sidebar-inset: 12px;
/* ======== DO NOT EDIT THIS VARIABLES! ======== */
--iw: calc(var(--open-icon-size) - var(--open-icon-line-variance));
--aw: calc(var(--iw) - var(--open-icon-line-variance));
--caret-size: calc(var(--dropdown-content-gap) + var(--dropdown-content-border-size));
--dropdown-content-border: solid var(--dropdown-content-border-color) var(--dropdown-content-border-size);
}
/* SIDEBAR BACK TEXT BAR HEIGHT WHEN BACK TEXT OVERLAYS LOGO */
#brx-header:has([data-sidebar-back-text-on-logo="true"]) {
--top-offset: var(--mobile-menu-top-height);
}
/* ========================
STICKY HEADER STYLES
add variables to modify for sticky header
======================== */
.sticky.scrolling {
/* add your sticky styles variable here */
}
/* ========================
DROPDOWN ITEM IS BUTTON OR ICON
======================== */
[data-is-button]>.brx-submenu-toggle {
--menu-item-bg: black;
--menu-item-clr: white;
--menu-item-hover-clr: white;
--menu-item-hover-bg: black;
--menu-item-radius: 50vw;
--menu-item-hover-border-height: 0;
--chevron-size: 0;
--chevron-color: white;
--menu-item-inline-padding: 1.5rem;
--menu-item-block-padding: 1rem;
/* mobile only*/
--menu-item-width: 200px;
--menu-item-border: solid 1px transparent;
--menu-item-hover-border: solid 1px transparent;
}
[data-is-icon]>.brx-submenu-toggle {
--menu-item-bg: black;
--menu-item-clr: white;
--menu-item-hover-clr: white;
--menu-item-hover-bg: black;
--icon-clr: white;
--icon-hover-clr: white;
--icon-size: 14px;
--menu-item-inline-padding: 1.1rem;
--button-max-diameter: 45px;
--menu-item-radius: 50vw;
--menu-item-border: solid 1px transparent;
--menu-item-hover-border: solid 1px transparent;
}
/* ========== NAV STYLES END ============== */
/* ======= STICKY HEADER WITH OVERLAY SPECIAL STYLES ===== */
/* SVG STYLES BEFORE SCROLLING - REVERTS TO INITIAL VALUES WHEN DROPDOWN IS OPEN */
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky:not(.scrolling) .dwc-nest-header:not(:has(.brxe-dropdown.open)) :is(path, .cls-1):not(:is(.brx-submenu-toggle path)) {
fill: white;
}
/* STYLES BEFORE SCROLLING - REVERTS TO INITIAL VALUES WHEN DROPDOWN IS OPEN */
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky:not(.scrolling) .dwc-nest-header:not(:has(.brxe-dropdown.open)) {
--menu-item-clr: white;
--chevron-clr: white;
--menu-item-hover-clr: white;
--menu-item-hover-border-bg: white;
--overlay-header-bg: transparent;
--overlay-header-shadow: none;
}
/* STYLES BEFORE SCROLLING */
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky:not(.scrolling) {
--link-transition: 0s;
--transition: 0.2s;
}
/* STYLES WHEN SCROLLING */
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky.scrolling {
--overlay-header-bg: white;
}
/* STYLES WHEN SCROLLING :HOVER*/
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky.scrolling .dwc-nest-header:has(.brxe-dropdown.open) {
/* add and update variable here */
}
/* DEFAULT STYLES FOR OVERLAY HEADER */
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend .sticky {
--menu-item-hover-bg: initial;
--menu-item-bg: initial;
--overlay-header-inset: 0;
--overlay-header-width: 100%;
--overlay-header-radius: 0;
}
/* RESET STICKY HEADER TRANSITION*/
html:not(.dwc-mobile):has([data-sticky-overlay-special-style="true"]) .bricks-is-frontend #brx-header.sticky .dwc-nest-header {
transition: var(--transition);
}
/* ======= END STICKY HEADER WITH OVERLAY SPECIAL STYLES ===== */
/* ======== CHANGE postid-xxxx TO THIS TEMPLATE'S ID ========= */
/*hide sidebar in builder */
:is(.brx-header-left, .brx-header-right)[data-builder-window] #brx-header:not(.postid-23338 *, :has(.dwc-sidebar) *) {
display: none !important;
}
[data-builder-window] #brx-header:has([data-offcanvas="true"]):not(.postid-23338 *, :has(.dwc-sidebar) *) :is(.dwc-nav-wrapper, .dwc-nest-menu) {
display: none !important;
}
:is(.brx-header-left, .brx-header-right)[data-builder-window] :is(#brx-content, #brx-footer):not(.postid-23338 *, :has(.dwc-sidebar) *) {
margin: unset !important;
}
[data-builder-mode]:is(.brx-header-left, .brx-header-right).postid-23338::before {
display: none
}
const MegaMenuCONFIG = {
// Minimum width threshold for desktop behavior,
// should be 1px larger than mobile (max-width) breakpoint in the "MENU Styles/Options" CSS code block
//IMPORTANT: Also change min-width in the "MEGA MENU Codes" CSS code block
minWidth: 1201,
// automatically open the current menu panel/dropdown (mobile, offcanvas & sidebar)
menuAutoExpansion: true,
swipeToClose: true,
// show or hide swipe to close hint
toolTip: true,
// adaptive height animation
adaptiveHeight: 0,
// Stripe menu animation
stripeStyle: 0,
headerSelector: '.dwc-nest-header',
nestMenuSelector: '.dwc-nest-menu',
closeNavOnClick: 1,
closeOnHashClickOnly: 0,
breakinToNavList: 0, /*do not enable if using last item is button*/
// Selectors (e.g. .class-name, #id_name, tag-name, .complex > selectors etc.) that should NOT close the navigation when clicked
// Separate multiple selectors with a comma e.g. '.class-one, #id_one, tag'
closeNavOnClickExclude: '.js-wpml-ls-item-toggle',
// New properties for dropdown positioning
shiftFactor: 1, // Factor to shift overflow
minOverflow: 10, // Minimum overflow threshold
reinitializeOnURLchange: false, //when using page transition API
//move backdrop overlay element Inside header
//placing it inside header will allow backdrop overlay/blur to affect header, but won't work when data-overlay-header is enabled.
overlayInsideHeader:0
};
// Centered Logo Configuration
const CenteredLogoCONFIG = {
enable: 0,
centerGuide: 1,
forceCenteredLogo: 1,
// move the navigation left
// or right using negative or positive value
centerNudge: 0,
// allow centered logo when no. of
// menu items are odd e.g. 5 or 7
allowOddItems: 1,
// place logo 'before' or 'after' the odd menu item
roundOffFactor: 'before'
};
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();
Cyberbeveiliging: de hiërarchie van behoeften
De Piramide van Cybersecurity Behoeften: Een nieuwe kijk op Maslow
Wat hebben de ‘Pyramide van Maslow’ en Cyberbeveiliging met elkaar gemeen? Om de top te bereiken, moet je beginnen bij de basis.
De theorie van Abraham Maslow, die in 1943 zijn beroemde hiërarchie van behoeften introduceerde, biedt een interessant perspectief op onze hedendaagse cybersecurity uitdagingen. Maslow’s theorie stelt dat mensen worden gemotiveerd door onvervulde behoeften. Deze behoeften zijn georganiseerd in een hiërarchische volgorde van overleving tot zelfverwerkelijking. Terwijl Maslow’s piramide zich concentreert op psychologische en fysiologische behoeften, nodigt de moderne digitale context ons uit om deze hiërarchie opnieuw te bekijken door de lens van cybersecurity.
Deze ‘ouderwetse’ en fundamentele principes van Maslow’s theorie kunnen wel degelijk toegepast worden op het huidige moderne cybersecurity. En ook op hoe een dergelijke aanpak kan helpen bij het prioriteren van de bescherming tegen digitale dreigingen. Het doel is om een kader te bieden dat organisaties en individuen helpt om hun digitale behoeften beter te begrijpen en te beveiligen in een steeds veranderende cyberomgeving.
Terug naar de basis!
Maslow identificeerde 5 categorieën van menselijke behoeften, georganiseerd in een piramidestructuur, van de meest fundamentele tot de meest geavanceerde:
Fysiologische of lichamelijke behoeften: Deze vormen de basis van de piramide en omvatten essentiële levensbehoeften zoals voedsel, water, en onderdak.
Veiligheid en zekerheid: Na de vervulling van de fysiologische behoeften, streven individuen naar veiligheid en bescherming in hun fysieke en financiële omgeving.
Sociaal contact: Mensen hebben ook behoefte aan relaties, liefde en het behoren tot een groep of gemeenschap.
Erkenning: Dit niveau omvat de behoefte aan zelfrespect, erkenning, en waardering van anderen.
Zelfontplooiing: De top van de piramide vertegenwoordigt het streven naar het realiseren van het eigen potentieel en zelfverwezenlijking.
Dezelfde opbouw en structuur van Maslow’s hiërarchie van behoeften is ook van toepassing op cybersecurity. Het biedt een uniek perspectief op hoe individuen en organisaties hun digitale veiligheid kunnen benaderen en verbeteren. Door deze behoeften als uitgangspunt te nemen, kunnen we een meer holistische benadering van cybersecurity ontwikkelen die verder gaat dan technische maatregelen alleen. Het stelt ons in staat om de menselijke aspecten van digitale veiligheid te begrijpen en te adresseren, en een veiligere digitale wereld te bouwen voor iedereen.
1. Fundamentele Beveiliging en Systemen
In de basis van Maslow’s hiërarchie van behoeften, die overeenkomt met fysiologische behoeften, vinden we de fundamentele cyberbeveiligingsbehoeften van organisaties. Deze basisbehoeften zijn essentieel voor het waarborgen van de veiligheid en stabiliteit van IT-systemen, net zoals fysiologische behoeften cruciaal zijn voor het menselijk overleven.
Basisbeveiliging: antivirus, firewalls, en basis cyberhygiëne – Deze elementen vormen de eerste verdedigingslinie tegen cyberdreigingen. Antivirussoftware beschermt tegen malware, terwijl firewalls ongeautoriseerde toegang tot netwerken blokkeren. Basis cyberhygiëne, zoals het gebruik van sterke wachtwoorden en het regelmatig bijwerken ervan, vermindert de kans op inbreuken. Net zoals voeding en water essentieel zijn voor het menselijk lichaam, zijn deze basisbeveiligingsmaatregelen cruciaal voor de gezondheid van IT-systemen.
Belang van regelmatige updates en patches voor software en systemen – Regelmatige updates zijn vergelijkbaar met het onderhouden van een gezond dieet voor het menselijk lichaam. Ze versterken de beveiliging door bekende kwetsbaarheden te dichten voordat aanvallers ze kunnen uitbuiten. Dit proces is essentieel voor het handhaven van de integriteit en prestaties van IT-systemen. Zonder regelmatige updates zouden systemen vatbaar zijn voor aanvallen, wat kan leiden tot gegevensverlies of -diefstal.
Deze fundamentele cyberbeveiligingsbehoeften zijn de basis waarop veiligere en meer geavanceerde praktijken worden gebouwd. Ze vormen de kern van een solide cyberbeveiligingsstrategie en zijn onmisbaar voor elke organisatie die haar informatie en die van haar klanten wil beschermen. Net zoals een stevig fundament cruciaal is voor de constructie van een gebouw, zijn deze basisbeveiligingsmaatregelen essentieel voor de digitale veiligheid van een organisatie.
2. Geavanceerde Beveiliging
Na de fundamentele inrichting van beveiliging en systemen, is er de geavanceerde beveiligingsmaatregelen. Dit gaat om EDR, netwerksegmentatie, toegangscontrole, en een doordachte backup- en herstelstrategie, een essentiële laag van bescherming in de piramide van cyberbeveiligingsbehoeften. Deze maatregelen stellen organisaties niet alleen in staat om zich te verdedigen tegen geavanceerde cyberdreigingen, maar ook om een veerkrachtige en responsieve beveiligingshouding te ontwikkelen die de integriteit en beschikbaarheid van hun systemen en data waarborgt.
EDR-systemen bieden real-time monitoring en analyse van gebeurtenissen op endpoints, waardoor snelle detectie en reactie op verdachte activiteiten mogelijk is. Door gedragingen te analyseren en te reageren op bedreigingen zodra ze zich voordoen, helpen EDR-oplossingen organisaties om proactief te reageren op cyberaanvallen, inclusief ransomware.
Netwerksegmentatie verbetert de beveiliging door het netwerk op te delen in kleinere, beheersbare segmenten. Dit beperkt de bewegingsvrijheid van een aanvaller binnen het netwerk, waardoor de verspreiding van malware wordt beperkt en de toegang tot gevoelige informatie wordt beschermd.
Toegangscontrole is een andere cruciale laag van geavanceerde beveiliging, die zorgt voor de juiste authenticatie en autorisatie van gebruikers. Door strikte toegangscontrolemaatregelen te implementeren, kunnen organisaties de toegang tot kritieke systemen en data beperken, waardoor het risico op ongeautoriseerde toegang en datalekken wordt verminderd.
Tot slot is de implementatie van een robuuste backup- en herstelstrategie van cruciaal belang om organisaties te beschermen tegen ransomware-aanvallen. Door regelmatig back-ups te maken van essentiële data en deze op een veilige, van het netwerk gescheiden locatie te bewaren, kunnen organisaties de impact van een ransomware-aanval minimaliseren en de continuïteit van de bedrijfsvoering waarborgen.
3. De menselijke factor
Cyberbeveiliging heeft altijd te maken met mensen en hoe individuen omgaan met cultuur, beleid en maatregelen. Hierin ligt zowel een groot risico als een grote kans.
Cultuur Het erkennen en cultiveren van een cultuur van cyberbewustzijn binnen organisaties is cruciaal. Dit vereist niet alleen regelmatige training en educatie maar ook een voortdurende dialoog tussen teams en met externe partners. Samenwerking en informatie-uitwisseling versterken de beveiligingshouding door collectieve kennis en middelen te bundelen.
Bewustzijn De menselijke neiging tot fouten maakt ons vatbaar voor phishing-aanvallen, waarbij oplichters vaak vertrouwen op de kunst van misleiding om toegang te verkrijgen tot beveiligde systemen. Onbewust delen van informatie, zoals via onbedoelde klikken of het delen van gevoelige data, verhoogt het risico op datalekken aanzienlijk. Daarom is het versterken van het bewustzijn en het bieden van uitgebreide training in cyberbeveiliging van essentieel belang om de kwetsbaarheid voor dergelijke aanvallen te verkleinen.
Samenwerking Insiderdreigingen vormen een unieke uitdaging; medewerkers kunnen, bewust of onbewust, de interne systemen in gevaar brengen. Dit risico wordt versterkt door weerstand tegen verandering, waarbij aarzeling om nieuwe beveiligingsmaatregelen te adopteren de deur open kan laten voor aanvallers. Samenwerkingsuitdagingen, zoals het gebrek aan effectieve communicatie en samenwerking, kunnen eveneens leiden tot beveiligingsbreuken.
Compliance Een ander belangrijk aspect is het naleven van compliance. Het negeren van beveiligingsbeleid en -richtlijnen kan ernstige kwetsbaarheden blootleggen. Tegelijkertijd kan overmatig vertrouwen in technologie, zonder adequate menselijke controle, onvoorziene beveiligingslekken veroorzaken. Privacyzorgen spelen ook een rol; de angst voor inbreuk op de privacy kan weerstand tegen beveiligingsmaatregelen voeden.
Training Om deze menselijke factoren effectief aan te pakken, moeten organisaties een evenwicht vinden tussen technologische oplossingen en menselijke interacties. Dit houdt in dat medewerkers worden opgeleid om de subtiele tekenen van phishing te herkennen, worden aangemoedigd om best practices te volgen, en zich bewust zijn van de implicaties van hun acties. Een cultuur waarin veiligheid wordt gezien als een gedeelde verantwoordelijkheid, kan een krachtig wapen zijn tegen cyberdreigingen.
In essentie vereist de menselijke factor in cyberbeveiliging een holistische benadering. Door medewerkers te beschouwen als essentiële stakeholders in de cyberbeveiligingsstrategie, kunnen organisaties een robuuste verdediging opbouwen die niet alleen technologische bedreigingen aanpakt maar ook de menselijke elementen die zo vaak over het hoofd worden gezien. Het is deze synergie tussen mens en machine die de basis vormt voor een veerkrachtige en effectieve cyberbeveiligingsstrategie.
4. Veilige en lerende cultuur
Deze laag vertaalt zich naar het belang van het erkennen en waarderen van veiligheidsinspanningen, het ontwikkelen van een veiligheidscultuur, het vergroten van bewustzijn en training, het implementeren van geavanceerde beveiligingstechnologieën, en actieve deelname aan cyberbeveiligingsgemeenschappen. Dit overstijgt de inspanningen van het individu en legt meer de nadruk op het belang van de inspanningen van de gehele organisatie en aansturing ervan.
Erkenning van veiligheidsinspanningen moedigt medewerkers aan om proactief bij te dragen aan de cyberveiligheid, terwijl de ontwikkeling van een veiligheidscultuur zorgt voor een fundament waarop veiligheidspraktijken kunnen floreren. Door het bewustzijn en de training te vergroten, worden werknemers uitgerust met de kennis en vaardigheden om cyberdreigingen te herkennen en te voorkomen. De implementatie van geavanceerde beveiligingstechnologieën beschermt tegen steeds evoluerende bedreigingen, en actieve deelname aan cyberbeveiligingsgemeenschappen bevordert de uitwisseling van kennis en best practices.
Deze laag onderstreept het belang van binnen- en buitenorganisatorische erkenning voor het bereiken van een hoger niveau van veiligheid, en het creëren van een omgeving waar continue verbetering van cyberbeveiligingspraktijken wordt aangemoedigd. Het is een essentiële stap naar het bouwen aan een robuuste cyberbeveiligingshouding die niet alleen beschermt tegen huidige bedreigingen maar ook voorbereid is op de uitdagingen van morgen.
5. Cyber Excellence
Cybersecurity maturiteit gaat over hoe geavanceerd en effectief de cybersecurity praktijken van een organisatie zijn. Het streven naar cyber excellence, de bovenste laag van de cybersecurity piramide, impliceert het bereiken van de hoogste staat van beveiligingsmaturiteit. Dit wordt gekenmerkt door een veilige en lerende cultuur, geïntegreerde beveiligingsstrategieën, voortdurende verbetering, en proactieve verdediging tegen bedreigingen. Belangrijke elementen omvatten onder andere sterke governance, risicobeheer, en een cultuur van veiligheidsbewustzijn.
Organisaties die cyber excellence hebben bereikt, worden vaak erkend voor hun vermogen om snel te reageren op nieuwe bedreigingen en voor hun commitment aan continue verbetering van hun cybersecurity praktijken. Het pad naar cyber excellence vereist een stapsgewijze aanpak, beginnend met de fundamenten van cybersecurity en geleidelijk opbouwend naar meer geavanceerde praktijken. Dit omvat het regelmatig beoordelen en bijwerken van beveiligingsbeleid, het trainen van personeel, en het investeren in geavanceerde beveiligingstechnologieën.
Het belang van het streven naar de bovenste laag van cybersecurity binnen de piramide van Maslow ligt in het feit dat, net zoals mensen niet kunnen functioneren zonder hun basisbehoeften te vervullen, organisaties niet veilig en effectief kunnen opereren zonder een solide basis in cybersecurity. Dit streven helpt niet alleen om gevoelige informatie en systemen te beschermen, maar ondersteunt ook het algehele succes en de duurzaamheid van de organisatie in het digitale tijdperk.
Bij penetratietesten, ook bekend als pentesten, wordt gebruikgemaakt van zoekmachines om kwetsbaarheden in systemen te identificeren. Deze gespecialiseerde zoekmachines verschillen…
LastPass kreeg in 2025 een boete van 1,4 miljoen euro opgelegd na een ernstige beveiligingsinbreuk waarbij miljoenen gebruikersdata werden buitgemaakt.…