PageSourceSearch

https://www.bcd.bzh/wp-content/themes/bcd2022/assets/js/scripts.js?ver=1.0.0

js bcd.bzh collected 2026-10-02 07:40:58 UTC 48,799 bytes, 1,237 lines download raw bytes

1// Utility function
2function Util () {};
3
4/* 
5	class manipulation functions
6*/
7Util.hasClass = function(el, className) {
8	return el.classList.contains(className);
9};
10
11Util.addClass = function(el, className) {
12	var classList = className.split(' ');
13 	el.classList.add(classList[0]);
14 	if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' '));
15};
16
17Util.removeClass = function(el, className) {
18	var classList = className.split(' ');
19	el.classList.remove(classList[0]);	
20	if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' '));
21};
22
23Util.toggleClass = function(el, className, bool) {
24	if(bool) Util.addClass(el, className);
25	else Util.removeClass(el, className);
26};
27
28Util.setAttributes = function(el, attrs) {
29  for(var key in attrs) {
30    el.setAttribute(key, attrs[key]);
31  }
32};
33
34/* 
35  DOM manipulation
36*/
37Util.getChildrenByClassName = function(el, className) {
38  var children = el.children,
39    childrenByClass = [];
40  for (var i = 0; i < children.length; i++) {
41    if (Util.hasClass(children[i], className)) childrenByClass.push(children[i]);
42  }
43  return childrenByClass;
44};
45
46Util.is = function(elem, selector) {
47  if(selector.nodeType){
48    return elem === selector;
49  }
50
51  var qa = (typeof(selector) === 'string' ? document.querySelectorAll(selector) : selector),
52    length = qa.length,
53    returnArr = [];
54
55  while(length--){
56    if(qa[length] === elem){
57      return true;
58    }
59  }
60
61  return false;
62};
63
64/* 
65	Animate height of an element
66*/
67Util.setHeight = function(start, to, element, duration, cb, timeFunction) {
68	var change = to - start,
69	    currentTime = null;
70
71  var animateHeight = function(timestamp){  
72    if (!currentTime) currentTime = timestamp;         
73    var progress = timestamp - currentTime;
74    if(progress > duration) progress = duration;
75    var val = parseInt((progress/duration)*change + start);
76    if(timeFunction) {
77      val = Math[timeFunction](progress, start, to - start, duration);
78    }
79    element.style.height = val+"px";
80    if(progress < duration) {
81        window.requestAnimationFrame(animateHeight);
82    } else {
83    	if(cb) cb();
84    }
85  };
86  
87  //set the height of the element before starting animation -> fix bug on Safari
88  element.style.height = start+"px";
89  window.requestAnimationFrame(animateHeight);
90};
91
92/* 
93	Smooth Scroll
94*/
95
96Util.scrollTo = function(final, duration, cb, scrollEl) {
97  var element = scrollEl || window;
98  var start = element.scrollTop || document.documentElement.scrollTop,
99    currentTime = null;
100
101  if(!scrollEl) start = window.scrollY || document.documentElement.scrollTop;
102      
103  var animateScroll = function(timestamp){
104  	if (!currentTime) currentTime = timestamp;        
105    var progress = timestamp - currentTime;
106    if(progress > duration) progress = duration;
107    var val = Math.easeInOutQuad(progress, start, final-start, duration);
108    element.scrollTo(0, val);
109    if(progress < duration) {
110      window.requestAnimationFrame(animateScroll);
111    } else {
112      cb && cb();
113    }
114  };
115
116  window.requestAnimationFrame(animateScroll);
117};
118
119/* 
120  Focus utility classes
121*/
122
123//Move focus to an element
124Util.moveFocus = function (element) {
125  if( !element ) element = document.getElementsByTagName("body")[0];
126  element.focus();
127  if (document.activeElement !== element) {
128    element.setAttribute('tabindex','-1');
129    element.focus();
130  }
131};
132
133/* 
134  Misc
135*/
136
137Util.getIndexInArray = function(array, el) {
138  return Array.prototype.indexOf.call(array, el);
139};
140
141Util.cssSupports = function(property, value) {
142  if('CSS' in window) {
143    return CSS.supports(property, value);
144  } else {
145    var jsProperty = property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase();});
146    return jsProperty in document.body.style;
147  }
148};
149
150// merge a set of user options into plugin defaults
151// https://gomakethings.com/vanilla-javascript-version-of-jquery-extend/
152Util.extend = function() {
153  // Variables
154  var extended = {};
155  var deep = false;
156  var i = 0;
157  var length = arguments.length;
158
159  // Check if a deep merge
160  if ( Object.prototype.toString.call( arguments[0] ) === '[object Boolean]' ) {
161    deep = arguments[0];
162    i++;
163  }
164
165  // Merge the object into the extended object
166  var merge = function (obj) {
167    for ( var prop in obj ) {
168      if ( Object.prototype.hasOwnProperty.call( obj, prop ) ) {
169        // If deep merge and property is an object, merge properties
170        if ( deep && Object.prototype.toString.call(obj[prop]) === '[object Object]' ) {
171          extended[prop] = extend( true, extended[prop], obj[prop] );
172        } else {
173          extended[prop] = obj[prop];
174        }
175      }
176    }
177  };
178
179  // Loop through each object and conduct a merge
180  for ( ; i < length; i++ ) {
181    var obj = arguments[i];
182    merge(obj);
183  }
184
185  return extended;
186};
187
188// Check if Reduced Motion is enabled
189Util.osHasReducedMotion = function() {
190  if(!window.matchMedia) return false;
191  var matchMediaObj = window.matchMedia('(prefers-reduced-motion: reduce)');
192  if(matchMediaObj) return matchMediaObj.matches;
193  return false; // return false if not supported
194}; 
195
196/* 
197	Polyfills
198*/
199//Closest() method
200if (!Element.prototype.matches) {
201	Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
202}
203
204if (!Element.prototype.closest) {
205	Element.prototype.closest = function(s) {
206		var el = this;
207		if (!document.documentElement.contains(el)) return null;
208		do {
209			if (el.matches(s)) return el;
210			el = el.parentElement || el.parentNode;
211		} while (el !== null && el.nodeType === 1); 
212		return null;
213	};
214}
215
216//Custom Event() constructor
217if ( typeof window.CustomEvent !== "function" ) {
218
219  function CustomEvent ( event, params ) {
220    params = params || { bubbles: false, cancelable: false, detail: undefined };
221    var evt = document.createEvent( 'CustomEvent' );
222    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
223    return evt;
224   }
225
226  CustomEvent.prototype = window.Event.prototype;
227
228  window.CustomEvent = CustomEvent;
229}
230
231/* 
232	Animation curves
233*/
234Math.easeInOutQuad = function (t, b, c, d) {
235	t /= d/2;
236	if (t < 1) return c/2*t*t + b;
237	t--;
238	return -c/2 * (t*(t-2) - 1) + b;
239};
240
241Math.easeInQuart = function (t, b, c, d) {
242	t /= d;
243	return c*t*t*t*t + b;
244};
245
246Math.easeOutQuart = function (t, b, c, d) { 
247  t /= d;
248	t--;
249	return -c * (t*t*t*t - 1) + b;
250};
251
252Math.easeInOutQuart = function (t, b, c, d) {
253	t /= d/2;
254	if (t < 1) return c/2*t*t*t*t + b;
255	t -= 2;
256	return -c/2 * (t*t*t*t - 2) + b;
257};
258
259Math.easeOutElastic = function (t, b, c, d) {
260  var s=1.70158;var p=d*0.7;var a=c;
261  if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
262  if (a < Math.abs(c)) { a=c; var s=p/4; }
263  else var s = p/(2*Math.PI) * Math.asin (c/a);
264  return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
265};
266
267
268/* JS Utility Classes */
269
270// make focus ring visible only for keyboard navigation (i.e., tab key) 
271(function() {
272  var focusTab = document.getElementsByClassName('js-tab-focus'),
273    shouldInit = false,
274    outlineStyle = false,
275    eventDetected = false;
276
277  function detectClick() {
278    if(focusTab.length > 0) {
279      resetFocusStyle(false);
280      window.addEventListener('keydown', detectTab);
281    }
282    window.removeEventListener('mousedown', detectClick);
283    outlineStyle = false;
284    eventDetected = true;
285  };
286
287  function detectTab(event) {
288    if(event.keyCode !== 9) return;
289    resetFocusStyle(true);
290    window.removeEventListener('keydown', detectTab);
291    window.addEventListener('mousedown', detectClick);
292    outlineStyle = true;
293  };
294
295  function resetFocusStyle(bool) {
296    var outlineStyle = bool ? '' : 'none';
297    for(var i = 0; i < focusTab.length; i++) {
298      focusTab[i].style.setProperty('outline', outlineStyle);
299    }
300  };
301
302  function initFocusTabs() {
303    if(shouldInit) {
304      if(eventDetected) resetFocusStyle(outlineStyle);
305      return;
306    }
307    shouldInit = focusTab.length > 0;
308    window.addEventListener('mousedown', detectClick);
309  };
310
311  initFocusTabs();
312  window.addEventListener('initFocusTabs', initFocusTabs);
313}());
314
315function resetFocusTabsStyle() {
316  window.dispatchEvent(new CustomEvent('initFocusTabs'));
317};
318// File#: _1_diagonal-movement
319// Usage: codyhouse.co/license
320/*
321  Modified version of the jQuery-menu-aim plugin
322  https://github.com/kamens/jQuery-menu-aim
323  - Replaced jQuery with Vanilla JS
324  - Minor changes
325*/
326(function () {
327    var menuAim = function (opts) {
328        init(opts);
329    };
330
331    window.menuAim = menuAim;
332
333    function init(opts) {
334        var activeRow = null,
335            mouseLocs = [],
336            lastDelayLoc = null,
337            timeoutId = null,
338            options = Util.extend({
339                menu: '',
340                rows: false, //if false, get direct children - otherwise pass nodes list 
341                submenuSelector: "*",
342                submenuDirection: "right",
343                tolerance: 75,  // bigger = more forgivey when entering submenu
344                enter: function () { },
345                exit: function () { },
346                activate: function () { },
347                deactivate: function () { },
348                exitMenu: function () { }
349            }, opts),
350            menu = options.menu;
351
352        var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track
353            DELAY = 300;  // ms delay when user appears to be entering submenu
354
355        /**
356         * Keep track of the last few locations of the mouse.
357         */
358        var mouseMoveFallback = function (event) {
359            (!window.requestAnimationFrame) ? mousemoveDocument(event) : window.requestAnimationFrame(function () { mousemoveDocument(event); });
360        };
361
362        var mousemoveDocument = function (e) {
363            mouseLocs.push({ x: e.pageX, y: e.pageY });
364
365            if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
366                mouseLocs.shift();
367            }
368        };
369
370        /**
371         * Cancel possible row activations when leaving the menu entirely
372         */
373        var mouseleaveMenu = function () {
374            if (timeoutId) {
375                clearTimeout(timeoutId);
376            }
377
378            // If exitMenu is supplied and returns true, deactivate the
379            // currently active row on menu exit.
380            if (options.exitMenu(this)) {
381                if (activeRow) {
382                    options.deactivate(activeRow);
383                }
384
385                activeRow = null;
386            }
387        };
388
389        /**
390         * Trigger a possible row activation whenever entering a new row.
391         */
392        var mouseenterRow = function () {
393            if (timeoutId) {
394                // Cancel any previous activation delays
395                clearTimeout(timeoutId);
396            }
397
398            options.enter(this);
399            possiblyActivate(this);
400        },
401            mouseleaveRow = function () {
402                options.exit(this);
403            };
404
405        /*
406         * Immediately activate a row if the user clicks on it.
407         */
408        var clickRow = function () {
409            activate(this);
410        };
411
412        /**
413         * Activate a menu row.
414         */
415        var activate = function (row) {
416            if (row == activeRow) {
417                return;
418            }
419
420            if (activeRow) {
421                options.deactivate(activeRow);
422            }
423
424            options.activate(row);
425            activeRow = row;
426        };
427
428        /**
429         * Possibly activate a menu row. If mouse movement indicates that we
430         * shouldn't activate yet because user may be trying to enter
431         * a submenu's content, then delay and check again later.
432         */
433        var possiblyActivate = function (row) {
434            var delay = activationDelay();
435
436            if (delay) {
437                timeoutId = setTimeout(function () {
438                    possiblyActivate(row);
439                }, delay);
440            } else {
441                activate(row);
442            }
443        };
444
445        /**
446         * Return the amount of time that should be used as a delay before the
447         * currently hovered row is activated.
448         *
449         * Returns 0 if the activation should happen immediately. Otherwise,
450         * returns the number of milliseconds that should be delayed before
451         * checking again to see if the row should be activated.
452         */
453        var activationDelay = function () {
454            if (!activeRow || !Util.is(activeRow, options.submenuSelector)) {
455                // If there is no other submenu row already active, then
456                // go ahead and activate immediately.
457                return 0;
458            }
459
460            function getOffset(element) {
461                var rect = element.getBoundingClientRect();
462                return { top: rect.top + window.pageYOffset, left: rect.left + window.pageXOffset };
463            };
464
465            var offset = getOffset(menu),
466                upperLeft = {
467                    x: offset.left,
468                    y: offset.top - options.tolerance
469                },
470                upperRight = {
471                    x: offset.left + menu.offsetWidth,
472                    y: upperLeft.y
473                },
474                lowerLeft = {
475                    x: offset.left,
476                    y: offset.top + menu.offsetHeight + options.tolerance
477                },
478                lowerRight = {
479                    x: offset.left + menu.offsetWidth,
480                    y: lowerLeft.y
481                },
482                loc = mouseLocs[mouseLocs.length - 1],
483                prevLoc = mouseLocs[0];
484
485            if (!loc) {
486                return 0;
487            }
488
489            if (!prevLoc) {
490                prevLoc = loc;
491            }
492
493            if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x || prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
494                // If the previous mouse location was outside of the entire
495                // menu's bounds, immediately activate.
496                return 0;
497            }
498
499            if (lastDelayLoc && loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
500                // If the mouse hasn't moved since the last time we checked
501                // for activation status, immediately activate.
502                return 0;
503            }
504
505            // Detect if the user is moving towards the currently activated
506            // submenu.
507            //
508            // If the mouse is heading relatively clearly towards
509            // the submenu's content, we should wait and give the user more
510            // time before activating a new row. If the mouse is heading
511            // elsewhere, we can immediately activate a new row.
512            //
513            // We detect this by calculating the slope formed between the
514            // current mouse location and the upper/lower right points of
515            // the menu. We do the same for the previous mouse location.
516            // If the current mouse location's slopes are
517            // increasing/decreasing appropriately compared to the
518            // previous's, we know the user is moving toward the submenu.
519            //
520            // Note that since the y-axis increases as the cursor moves
521            // down the screen, we are looking for the slope between the
522            // cursor and the upper right corner to decrease over time, not
523            // increase (somewhat counterintuitively).
524            function slope(a, b) {
525                return (b.y - a.y) / (b.x - a.x);
526            };
527
528            var decreasingCorner = upperRight,
529                increasingCorner = lowerRight;
530
531            // Our expectations for decreasing or increasing slope values
532            // depends on which direction the submenu opens relative to the
533            // main menu. By default, if the menu opens on the right, we
534            // expect the slope between the cursor and the upper right
535            // corner to decrease over time, as explained above. If the
536            // submenu opens in a different direction, we change our slope
537            // expectations.
538            if (options.submenuDirection == "left") {
539                decreasingCorner = lowerLeft;
540                increasingCorner = upperLeft;
541            } else if (options.submenuDirection == "below") {
542                decreasingCorner = lowerRight;
543                increasingCorner = lowerLeft;
544            } else if (options.submenuDirection == "above") {
545                decreasingCorner = upperLeft;
546                increasingCorner = upperRight;
547            }
548
549            var decreasingSlope = slope(loc, decreasingCorner),
550                increasingSlope = slope(loc, increasingCorner),
551                prevDecreasingSlope = slope(prevLoc, decreasingCorner),
552                prevIncreasingSlope = slope(prevLoc, increasingCorner);
553
554            if (decreasingSlope < prevDecreasingSlope && increasingSlope > prevIncreasingSlope) {
555                // Mouse is moving from previous location towards the
556                // currently activated submenu. Delay before activating a
557                // new menu row, because user may be moving into submenu.
558                lastDelayLoc = loc;
559                return DELAY;
560            }
561
562            lastDelayLoc = null;
563            return 0;
564        };
565
566        var reset = function (triggerDeactivate) {
567            if (timeoutId) {
568                clearTimeout(timeoutId);
569            }
570
571            if (activeRow && triggerDeactivate) {
572                options.deactivate(activeRow);
573            }
574
575            activeRow = null;
576        };
577
578        var destroyInstance = function () {
579            menu.removeEventListener('mouseleave', mouseleaveMenu);
580            document.removeEventListener('mousemove', mouseMoveFallback);
581            if (rows.length > 0) {
582                for (var i = 0; i < rows.length; i++) {
583                    rows[i].removeEventListener('mouseenter', mouseenterRow);
584                    rows[i].removeEventListener('mouseleave', mouseleaveRow);
585                    rows[i].removeEventListener('click', clickRow);
586                }
587            }
588
589        };
590
591        /**
592         * Hook up initial menu events
593         */
594        menu.addEventListener('mouseleave', mouseleaveMenu);
595        var rows = (options.rows) ? options.rows : menu.children;
596        if (rows.length > 0) {
597            for (var i = 0; i < rows.length; i++) {
598                (function (i) {
599                    rows[i].addEventListener('mouseenter', mouseenterRow);
600                    rows[i].addEventListener('mouseleave', mouseleaveRow);
601                    rows[i].addEventListener('click', clickRow);
602                })(i);
603            }
604        }
605
606        document.addEventListener('mousemove', mouseMoveFallback);
607
608        /* Reset/destroy menu */
609        menu.addEventListener('reset', function (event) {
610            reset(event.detail);
611        });
612        menu.addEventListener('destroy', destroyInstance);
613    };
614}());
615
616
617// File#: _1_tabs
618// Usage: codyhouse.co/license
619(function () {
620    var Tab = function (element) {
621        this.element = element;
622        this.tabList = this.element.getElementsByClassName('js-tabs__controls')[0];
623        this.listItems = this.tabList.getElementsByTagName('li');
624        this.triggers = this.tabList.getElementsByTagName('a');
625        this.panelsList = this.element.getElementsByClassName('js-tabs__panels')[0];
626        this.panels = Util.getChildrenByClassName(this.panelsList, 'js-tabs__panel');
627        this.hideClass = 'is-hidden';
628        this.customShowClass = this.element.getAttribute('data-show-panel-class') ? this.element.getAttribute('data-show-panel-class') : false;
629        this.layout = this.element.getAttribute('data-tabs-layout') ? this.element.getAttribute('data-tabs-layout') : 'horizontal';
630        // deep linking options
631        this.deepLinkOn = this.element.getAttribute('data-deep-link') == 'on';
632        // init tabs
633        this.initTab();
634    };
635
636    Tab.prototype.initTab = function () {
637        //set initial aria attributes
638        this.tabList.setAttribute('role', 'tablist');
639        for (var i = 0; i < this.triggers.length; i++) {
640            var bool = (i == 0),
641                panelId = this.panels[i].getAttribute('id');
642            this.listItems[i].setAttribute('role', 'presentation');
643            Util.setAttributes(this.triggers[i], { 'role': 'tab', 'aria-selected': bool, 'aria-controls': panelId, 'id': 'tab-' + panelId });
644            Util.addClass(this.triggers[i], 'js-tabs__trigger');
645            Util.setAttributes(this.panels[i], { 'role': 'tabpanel', 'aria-labelledby': 'tab-' + panelId });
646            Util.toggleClass(this.panels[i], this.hideClass, !bool);
647
648            if (!bool) this.triggers[i].setAttribute('tabindex', '-1');
649        }
650
651        //listen for Tab events
652        this.initTabEvents();
653
654        // check deep linking option
655        this.initDeepLink();
656    };
657
658    Tab.prototype.initTabEvents = function () {
659        var self = this;
660        //click on a new tab -> select content
661        this.tabList.addEventListener('click', function (event) {
662            if (event.target.closest('.js-tabs__trigger')) self.triggerTab(event.target.closest('.js-tabs__trigger'), event);
663        });
664        //arrow keys to navigate through tabs 
665        this.tabList.addEventListener('keydown', function (event) {
666            ;
667            if (!event.target.closest('.js-tabs__trigger')) return;
668            if (tabNavigateNext(event, self.layout)) {
669                event.preventDefault();
670                self.selectNewTab('next');
671            } else if (tabNavigatePrev(event, self.layout)) {
672                event.preventDefault();
673                self.selectNewTab('prev');
674            }
675        });
676    };
677
678    Tab.prototype.selectNewTab = function (direction) {
679        var selectedTab = this.tabList.querySelector('[aria-selected="true"]'),
680            index = Util.getIndexInArray(this.triggers, selectedTab);
681        index = (direction == 'next') ? index + 1 : index - 1;
682        //make sure index is in the correct interval 
683        //-> from last element go to first using the right arrow, from first element go to last using the left arrow
684        if (index < 0) index = this.listItems.length - 1;
685        if (index >= this.listItems.length) index = 0;
686        this.triggerTab(this.triggers[index]);
687        this.triggers[index].focus();
688    };
689
690    Tab.prototype.triggerTab = function (tabTrigger, event) {
691        var self = this;
692        event && event.preventDefault();
693        var index = Util.getIndexInArray(this.triggers, tabTrigger);
694        //no need to do anything if tab was already selected
695        if (this.triggers[index].getAttribute('aria-selected') == 'true') return;
696
697        for (var i = 0; i < this.triggers.length; i++) {
698            var bool = (i == index);
699            Util.toggleClass(this.panels[i], this.hideClass, !bool);
700            if (this.customShowClass) Util.toggleClass(this.panels[i], this.customShowClass, bool);
701            this.triggers[i].setAttribute('aria-selected', bool);
702            bool ? this.triggers[i].setAttribute('tabindex', '0') : this.triggers[i].setAttribute('tabindex', '-1');
703        }
704
705        // update url if deepLink is on
706        if (this.deepLinkOn) {
707            history.replaceState(null, '', '#' + tabTrigger.getAttribute('aria-controls'));
708        }
709    };
710
711    Tab.prototype.initDeepLink = function () {
712        if (!this.deepLinkOn) return;
713        var hash = window.location.hash.substr(1);
714        var self = this;
715        if (!hash || hash == '') return;
716        for (var i = 0; i < this.panels.length; i++) {
717            if (this.panels[i].getAttribute('id') == hash) {
718                this.triggerTab(this.triggers[i], false);
719                setTimeout(function () { self.panels[i].scrollIntoView(true); });
720                break;
721            }
722        };
723    };
724
725    function tabNavigateNext(event, layout) {
726        if (layout == 'horizontal' && (event.keyCode && event.keyCode == 39 || event.key && event.key == 'ArrowRight')) { return true; }
727        else if (layout == 'vertical' && (event.keyCode && event.keyCode == 40 || event.key && event.key == 'ArrowDown')) { return true; }
728        else { return false; }
729    };
730
731    function tabNavigatePrev(event, layout) {
732        if (layout == 'horizontal' && (event.keyCode && event.keyCode == 37 || event.key && event.key == 'ArrowLeft')) { return true; }
733        else if (layout == 'vertical' && (event.keyCode && event.keyCode == 38 || event.key && event.key == 'ArrowUp')) { return true; }
734        else { return false; }
735    };
736
737    window.Tab = Tab;
738
739    //initialize the Tab objects
740    var tabs = document.getElementsByClassName('js-tabs');
741    if (tabs.length > 0) {
742        for (var i = 0; i < tabs.length; i++) {
743            (function (i) { new Tab(tabs[i]); })(i);
744        }
745    }
746}());
747// File#: _2_dropdown
748// Usage: codyhouse.co/license
749(function () {
750    var Dropdown = function (element) {
751        this.element = element;
752        this.trigger = this.element.getElementsByClassName('js-dropdown__trigger')[0];
753        this.dropdown = this.element.getElementsByClassName('js-dropdown__menu')[0];
754        this.triggerFocus = false;
755        this.dropdownFocus = false;
756        this.hideInterval = false;
757        // sublevels
758        this.dropdownSubElements = this.element.getElementsByClassName('js-dropdown__sub-wrapper');
759        this.prevFocus = false; // store element that was in focus before focus changed
760        this.addDropdownEvents();
761    };
762
763    Dropdown.prototype.addDropdownEvents = function () {
764        //place dropdown
765        var self = this;
766        this.placeElement();
767        this.element.addEventListener('placeDropdown', function (event) {
768            self.placeElement();
769        });
770        // init dropdown
771        this.initElementEvents(this.trigger, this.triggerFocus); // this is used to trigger the primary dropdown
772        this.initElementEvents(this.dropdown, this.dropdownFocus); // this is used to trigger the primary dropdown
773        // init sublevels
774        this.initSublevels(); // if there are additional sublevels -> bind hover/focus events
775    };
776
777    Dropdown.prototype.placeElement = function () {
778        // remove inline style first
779        this.dropdown.removeAttribute('style');
780        // check dropdown position
781        var triggerPosition = this.trigger.getBoundingClientRect(),
782            isRight = (window.innerWidth < triggerPosition.left + parseInt(getComputedStyle(this.dropdown).getPropertyValue('width')));
783
784        var xPosition = isRight ? 'right: 0px; left: auto;' : 'left: 0px; right: auto;';
785        this.dropdown.setAttribute('style', xPosition);
786    };
787
788    Dropdown.prototype.initElementEvents = function (element, bool) {
789        var self = this;
790        element.addEventListener('mouseenter', function () {
791            bool = true;
792            self.showDropdown();
793        });
794        element.addEventListener('focus', function () {
795            self.showDropdown();
796        });
797        element.addEventListener('mouseleave', function () {
798            bool = false;
799            self.hideDropdown();
800        });
801        element.addEventListener('focusout', function () {
802            self.hideDropdown();
803        });
804    };
805
806    Dropdown.prototype.showDropdown = function () {
807        if (this.hideInterval) clearInterval(this.hideInterval);
808        // remove style attribute
809        this.dropdown.removeAttribute('style');
810        this.placeElement();
811        this.showLevel(this.dropdown, true);
812    };
813
814    Dropdown.prototype.hideDropdown = function () {
815        var self = this;
816        if (this.hideInterval) clearInterval(this.hideInterval);
817        this.hideInterval = setTimeout(function () {
818            var dropDownFocus = document.activeElement.closest('.js-dropdown'),
819                inFocus = dropDownFocus && (dropDownFocus == self.element);
820            // if not in focus and not hover -> hide
821            if (!self.triggerFocus && !self.dropdownFocus && !inFocus) {
822                self.hideLevel(self.dropdown, true);
823                // make sure to hide sub/dropdown
824                self.hideSubLevels();
825                self.prevFocus = false;
826            }
827        }, 300);
828    };
829
830    Dropdown.prototype.initSublevels = function () {
831        var self = this;
832        var dropdownMenu = this.element.getElementsByClassName('js-dropdown__menu');
833        for (var i = 0; i < dropdownMenu.length; i++) {
834            var listItems = dropdownMenu[i].children;
835            // bind hover
836            new menuAim({
837                menu: dropdownMenu[i],
838                activate: function (row) {
839                    var subList = row.getElementsByClassName('js-dropdown__menu')[0];
840                    if (!subList) return;
841                    Util.addClass(row.querySelector('a'), 'dropdown__item--hover');
842                    self.showLevel(subList);
843                },
844                deactivate: function (row) {
845                    var subList = row.getElementsByClassName('dropdown__menu')[0];
846                    if (!subList) return;
847                    Util.removeClass(row.querySelector('a'), 'dropdown__item--hover');
848                    self.hideLevel(subList);
849                },
850                submenuSelector: '.js-dropdown__sub-wrapper',
851            });
852        }
853        // store focus element before change in focus
854        this.element.addEventListener('keydown', function (event) {
855            if (event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab') {
856                self.prevFocus = document.activeElement;
857            }
858        });
859        // make sure that sublevel are visible when their items are in focus
860        this.element.addEventListener('keyup', function (event) {
861            if (event.keyCode && event.keyCode == 9 || event.key && event.key == 'Tab') {
862                // focus has been moved -> make sure the proper classes are added to subnavigation
863                var focusElement = document.activeElement,
864                    focusElementParent = focusElement.closest('.js-dropdown__menu'),
865                    focusElementSibling = focusElement.nextElementSibling;
866
867                // if item in focus is inside submenu -> make sure it is visible
868                if (focusElementParent && !Util.hasClass(focusElementParent, 'dropdown__menu--is-visible')) {
869                    self.showLevel(focusElementParent);
870                }
871                // if item in focus triggers a submenu -> make sure it is visible
872                if (focusElementSibling && !Util.hasClass(focusElementSibling, 'dropdown__menu--is-visible')) {
873                    self.showLevel(focusElementSibling);
874                }
875
876                // check previous element in focus -> hide sublevel if required 
877                if (!self.prevFocus) return;
878                var prevFocusElementParent = self.prevFocus.closest('.js-dropdown__menu'),
879                    prevFocusElementSibling = self.prevFocus.nextElementSibling;
880
881                if (!prevFocusElementParent) return;
882
883                // element in focus and element prev in focus are siblings
884                if (focusElementParent && focusElementParent == prevFocusElementParent) {
885                    if (prevFocusElementSibling) self.hideLevel(prevFocusElementSibling);
886                    return;
887                }
888
889                // element in focus is inside submenu triggered by element prev in focus
890                if (prevFocusElementSibling && focusElementParent && focusElementParent == prevFocusElementSibling) return;
891
892                // shift tab -> element in focus triggers the submenu of the element prev in focus
893                if (focusElementSibling && prevFocusElementParent && focusElementSibling == prevFocusElementParent) return;
894
895                var focusElementParentParent = focusElementParent.parentNode.closest('.js-dropdown__menu');
896
897                // shift tab -> element in focus is inside the dropdown triggered by a siblings of the element prev in focus
898                if (focusElementParentParent && focusElementParentParent == prevFocusElementParent) {
899                    if (prevFocusElementSibling) self.hideLevel(prevFocusElementSibling);
900                    return;
901                }
902
903                if (prevFocusElementParent && Util.hasClass(prevFocusElementParent, 'dropdown__menu--is-visible')) {
904                    self.hideLevel(prevFocusElementParent);
905                }
906            }
907        });
908    };
909
910    Dropdown.prototype.hideSubLevels = function () {
911        var visibleSubLevels = this.dropdown.getElementsByClassName('dropdown__menu--is-visible');
912        if (visibleSubLevels.length == 0) return;
913        while (visibleSubLevels[0]) {
914            this.hideLevel(visibleSubLevels[0]);
915        }
916        var hoveredItems = this.dropdown.getElementsByClassName('dropdown__item--hover');
917        while (hoveredItems[0]) {
918            Util.removeClass(hoveredItems[0], 'dropdown__item--hover');
919        }
920    };
921
922    Dropdown.prototype.showLevel = function (level, bool) {
923        if (bool == undefined) {
924            //check if the sublevel needs to be open to the left
925            Util.removeClass(level, 'dropdown__menu--left');
926            var boundingRect = level.getBoundingClientRect();
927            if (window.innerWidth - boundingRect.right < 5 && boundingRect.left + window.scrollX > 2 * boundingRect.width) Util.addClass(level, 'dropdown__menu--left');
928        }
929        Util.addClass(level, 'dropdown__menu--is-visible');
930        Util.removeClass(level, 'dropdown__menu--is-hidden');
931    };
932
933    Dropdown.prototype.hideLevel = function (level, bool) {
934        if (!Util.hasClass(level, 'dropdown__menu--is-visible')) return;
935        Util.removeClass(level, 'dropdown__menu--is-visible');
936        Util.addClass(level, 'dropdown__menu--is-hidden');
937
938        level.addEventListener('transitionend', function cb(event) {
939            if (event.propertyName != 'opacity') return;
940            level.removeEventListener('transitionend', cb);
941            if (Util.hasClass(level, 'dropdown__menu--is-hidden')) Util.removeClass(level, 'dropdown__menu--is-hidden dropdown__menu--left');
942            if (bool && !Util.hasClass(level, 'dropdown__menu--is-visible')) level.setAttribute('style', 'width: 0px; overflow: hidden;');
943        });
944    };
945
946    window.Dropdown = Dropdown;
947
948    var dropdown = document.getElementsByClassName('js-dropdown');
949    if (dropdown.length > 0) { // init Dropdown objects
950        for (var i = 0; i < dropdown.length; i++) {
951            (function (i) { new Dropdown(dropdown[i]); })(i);
952        }
953    }
954}());
955// File#: _3_mega-site-navigation
956// Usage: codyhouse.co/license
957(function () {
958    var MegaNav = function (element) {
959        this.element = element;
960        this.search = this.element.getElementsByClassName('js-mega-nav__search');
961        this.searchActiveController = false;
962        this.menu = this.element.getElementsByClassName('js-mega-nav__nav');
963        this.menuItems = this.menu[0].getElementsByClassName('js-mega-nav__item');
964        this.menuActiveController = false;
965        this.itemExpClass = 'mega-nav__item--expanded';
966        this.classIconBtn = 'mega-nav__icon-btn--state-b';
967        this.classSearchVisible = 'mega-nav__search--is-visible';
968        this.classNavVisible = 'mega-nav__nav--is-visible';
969        this.classMobileLayout = 'mega-nav--mobile';
970        this.classDesktopLayout = 'mega-nav--desktop';
971        this.layout = 'mobile';
972        // store dropdown elements (if present)
973        this.dropdown = this.element.getElementsByClassName('js-dropdown');
974        // expanded class - added to header when subnav is open
975        this.expandedClass = 'mega-nav--expanded';
976        // check if subnav should open on hover
977        this.hover = this.element.getAttribute('data-hover') && this.element.getAttribute('data-hover') == 'on';
978        initMegaNav(this);
979    };
980
981    function initMegaNav(megaNav) {
982        setMegaNavLayout(megaNav); // switch between mobile/desktop layout
983        initSearch(megaNav); // controll search navigation
984        initMenu(megaNav); // control main menu nav - mobile only
985        initSubNav(megaNav); // toggle sub navigation visibility
986
987        megaNav.element.addEventListener('update-menu-layout', function (event) {
988            setMegaNavLayout(megaNav); // window resize - update layout
989        });
990    };
991
992    function setMegaNavLayout(megaNav) {
993        var layout = getComputedStyle(megaNav.element, ':before').getPropertyValue('content').replace(/\'|"/g, '');
994        if (layout == megaNav.layout) return;
995        megaNav.layout = layout;
996        Util.toggleClass(megaNav.element, megaNav.classDesktopLayout, megaNav.layout == 'desktop');
997        Util.toggleClass(megaNav.element, megaNav.classMobileLayout, megaNav.layout != 'desktop');
998        if (megaNav.layout == 'desktop') {
999            closeSubNav(megaNav, false);
1000            // if the mega navigation has dropdown elements -> make sure they are in the right position (viewport awareness)
1001            triggerDropdownPosition(megaNav);
1002        }
1003        closeSearch(megaNav, false);
1004        resetMegaNavOffset(megaNav); // reset header offset top value
1005        resetNavAppearance(megaNav); // reset nav expanded appearance
1006    };
1007
1008    function resetMegaNavOffset(megaNav) {
1009        document.documentElement.style.setProperty('--mega-nav-offset-y', megaNav.element.getBoundingClientRect().top + 'px');
1010    };
1011
1012    function closeNavigation(megaNav) { // triggered by Esc key press
1013        // close search
1014        closeSearch(megaNav);
1015        // close nav
1016        if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible)) {
1017            toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, true);
1018        }
1019        //close subnav 
1020        closeSubNav(megaNav, false);
1021        resetNavAppearance(megaNav); // reset nav expanded appearance
1022    };
1023
1024    function closeFocusNavigation(megaNav) { // triggered by Tab key pressed
1025        // close search when focus is lost
1026        if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible) && !document.activeElement.closest('.js-mega-nav__search')) {
1027            toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, true);
1028        }
1029        // close nav when focus is lost
1030        if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible) && !document.activeElement.closest('.js-mega-nav__nav')) {
1031            toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, true);
1032        }
1033        // close subnav when focus is lost
1034        for (var i = 0; i < megaNav.menuItems.length; i++) {
1035            if (!Util.hasClass(megaNav.menuItems[i], megaNav.itemExpClass)) continue;
1036            var parentItem = document.activeElement.closest('.js-mega-nav__item');
1037            if (parentItem && parentItem == megaNav.menuItems[i]) continue;
1038            closeSingleSubnav(megaNav, i);
1039        }
1040        resetNavAppearance(megaNav); // reset nav expanded appearance
1041    };
1042
1043    function closeSearch(megaNav, bool) {
1044        if (megaNav.search.length < 1) return;
1045        if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible)) {
1046            toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, bool);
1047        }
1048    };
1049
1050    function initSearch(megaNav) {
1051        if (megaNav.search.length == 0) return;
1052        // toggle search
1053        megaNav.searchToggles = document.querySelectorAll('[aria-controls="' + megaNav.search[0].getAttribute('id') + '"]');
1054        for (var i = 0; i < megaNav.searchToggles.length; i++) {
1055            (function (i) {
1056                megaNav.searchToggles[i].addEventListener('click', function (event) {
1057                    // toggle search
1058                    toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchToggles[i], true);
1059                    // close nav if it was open
1060                    if (Util.hasClass(megaNav.menu[0], megaNav.classNavVisible)) {
1061                        toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuActiveController, false);
1062                    }
1063                    // close subnavigation if open
1064                    closeSubNav(megaNav, false);
1065                    resetNavAppearance(megaNav); // reset nav expanded appearance
1066                });
1067            })(i);
1068        }
1069    };
1070
1071    function initMenu(megaNav) {
1072        if (megaNav.menu.length == 0) return;
1073        // toggle nav
1074        megaNav.menuToggles = document.querySelectorAll('[aria-controls="' + megaNav.menu[0].getAttribute('id') + '"]');
1075        for (var i = 0; i < megaNav.menuToggles.length; i++) {
1076            (function (i) {
1077                megaNav.menuToggles[i].addEventListener('click', function (event) {
1078                    // toggle nav
1079                    toggleMenu(megaNav, megaNav.menu[0], 'menuActiveController', megaNav.classNavVisible, megaNav.menuToggles[i], true);
1080                    // close search if it was open
1081                    if (Util.hasClass(megaNav.search[0], megaNav.classSearchVisible)) {
1082                        toggleMenu(megaNav, megaNav.search[0], 'searchActiveController', megaNav.classSearchVisible, megaNav.searchActiveController, false);
1083                    }
1084                    resetNavAppearance(megaNav); // reset nav expanded appearance
1085                });
1086            })(i);
1087        }
1088    };
1089
1090    function toggleMenu(megaNav, element, controller, visibleClass, toggle, moveFocus) {
1091        var menuIsVisible = Util.hasClass(element, visibleClass);
1092        Util.toggleClass(element, visibleClass, !menuIsVisible);
1093        Util.toggleClass(toggle, megaNav.classIconBtn, !menuIsVisible);
1094        menuIsVisible ? toggle.removeAttribute('aria-expanded') : toggle.setAttribute('aria-expanded', 'true');
1095        if (menuIsVisible) {
1096            if (toggle && moveFocus) toggle.focus();
1097            megaNav[controller] = false;
1098        } else {
1099            if (toggle) megaNav[controller] = toggle;
1100            getFirstFocusable(element).focus(); // move focus to first focusable element
1101        }
1102    };
1103
1104    function getFirstFocusable(element) {
1105        var focusableEle = element.querySelectorAll('[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, [tabindex]:not([tabindex="-1"]), [contenteditable], audio[controls], video[controls], summary'),
1106            firstFocusable = false;
1107        for (var i = 0; i < focusableEle.length; i++) {
1108            if (focusableEle[i].offsetWidth || focusableEle[i].offsetHeight || focusableEle[i].getClientRects().length) {
1109                firstFocusable = focusableEle[i];
1110                break;
1111            }
1112        }
1113        return firstFocusable;
1114    };
1115
1116    function initSubNav(megaNav) {
1117        // toggle subnavigation visibility
1118        megaNav.element.addEventListener('click', function (event) {
1119            toggleSubNav(megaNav, event, 'click');
1120        });
1121
1122        if (megaNav.hover) { // data-hover="on" => use mouse events 
1123            megaNav.element.addEventListener('mouseover', function (event) {
1124                if (megaNav.layout != 'desktop') return;
1125                toggleSubNav(megaNav, event, 'mouseover')
1126            });
1127
1128            megaNav.element.addEventListener('mouseout', function (event) {
1129                if (megaNav.layout != 'desktop') return;
1130                var mainItem = event.target.closest('.js-mega-nav__item');
1131                if (!mainItem) return;
1132                var triggerBtn = mainItem.getElementsByClassName('js-mega-nav__control');
1133                if (triggerBtn.length < 1) return;
1134                var itemExpanded = Util.hasClass(mainItem, megaNav.itemExpClass);
1135                if (!itemExpanded) return;
1136                var mainItemHover = event.relatedTarget;
1137                if (mainItemHover && mainItem.contains(mainItemHover)) return;
1138
1139                Util.toggleClass(mainItem, megaNav.itemExpClass, !itemExpanded);
1140                itemExpanded ? triggerBtn[0].removeAttribute('aria-expanded') : triggerBtn[0].setAttribute('aria-expanded', 'true');
1141            });
1142        }
1143    };
1144
1145    function toggleSubNav(megaNav, event, eventType) {
1146        var triggerBtn = event.target.closest('.js-mega-nav__control');
1147        if (!triggerBtn) return;
1148        var mainItem = triggerBtn.closest('.js-mega-nav__item');
1149        if (!mainItem) return;
1150        var itemExpanded = Util.hasClass(mainItem, megaNav.itemExpClass);
1151        if (megaNav.hover && itemExpanded && megaNav.layout == 'desktop' && eventType != 'click') return;
1152        Util.toggleClass(mainItem, megaNav.itemExpClass, !itemExpanded);
1153        itemExpanded ? triggerBtn.removeAttribute('aria-expanded') : triggerBtn.setAttribute('aria-expanded', 'true');
1154        if (megaNav.layout == 'desktop' && !itemExpanded) closeSubNav(megaNav, mainItem);
1155        // close search if open
1156        closeSearch(megaNav, false);
1157        resetNavAppearance(megaNav); // reset nav expanded appearance
1158    };
1159
1160    function closeSubNav(megaNav, selectedItem) {
1161        // close subnav when a new sub nav element is open
1162        if (megaNav.menuItems.length == 0) return;
1163        for (var i = 0; i < megaNav.menuItems.length; i++) {
1164            if (megaNav.menuItems[i] != selectedItem) closeSingleSubnav(megaNav, i);
1165        }
1166    };
1167
1168    function closeSingleSubnav(megaNav, index) {
1169        Util.removeClass(megaNav.menuItems[index], megaNav.itemExpClass);
1170        var triggerBtn = megaNav.menuItems[index].getElementsByClassName('js-mega-nav__control');
1171        if (triggerBtn.length > 0) triggerBtn[0].removeAttribute('aria-expanded');
1172    };
1173
1174    function triggerDropdownPosition(megaNav) {
1175        // emit custom event to properly place dropdown elements - viewport awarness
1176        if (megaNav.dropdown.length == 0) return;
1177        for (var i = 0; i < megaNav.dropdown.length; i++) {
1178            megaNav.dropdown[i].dispatchEvent(new CustomEvent('placeDropdown'));
1179        }
1180    };
1181
1182    function resetNavAppearance(megaNav) {
1183        ((megaNav.element.getElementsByClassName(megaNav.itemExpClass).length > 0 && megaNav.layout == 'desktop') || megaNav.element.getElementsByClassName(megaNav.classSearchVisible).length > 0 || (megaNav.element.getElementsByClassName(megaNav.classNavVisible).length > 0 && megaNav.layout == 'mobile'))
1184            ? Util.addClass(megaNav.element, megaNav.expandedClass)
1185            : Util.removeClass(megaNav.element, megaNav.expandedClass);
1186    };
1187
1188    //initialize the MegaNav objects
1189    var megaNav = document.getElementsByClassName('js-mega-nav');
1190    if (megaNav.length > 0) {
1191        var megaNavArray = [];
1192        for (var i = 0; i < megaNav.length; i++) {
1193            (function (i) { megaNavArray.push(new MegaNav(megaNav[i])); })(i);
1194        }
1195
1196        // key events
1197        window.addEventListener('keyup', function (event) {
1198            if ((event.keyCode && event.keyCode == 27) || (event.key && event.key.toLowerCase() == 'escape')) { // listen for esc key events
1199                for (var i = 0; i < megaNavArray.length; i++) {
1200                    (function (i) {
1201                        closeNavigation(megaNavArray[i]);
1202                    })(i);
1203                }
1204            }
1205            // listen for tab key
1206            if ((event.keyCode && event.keyCode == 9) || (event.key && event.key.toLowerCase() == 'tab')) { // close search or nav if it looses focus
1207                for (var i = 0; i < megaNavArray.length; i++) {
1208                    (function (i) {
1209                        closeFocusNavigation(megaNavArray[i]);
1210                    })(i);
1211                }
1212            }
1213        });
1214
1215        window.addEventListener('click', function (event) {
1216            if (!event.target.closest('.js-mega-nav')) closeNavigation(megaNavArray[0]);
1217        });
1218
1219        // resize - update menu layout
1220        var resizingId = false,
1221            customEvent = new CustomEvent('update-menu-layout');
1222        window.addEventListener('resize', function (event) {
1223            clearTimeout(resizingId);
1224            resizingId = setTimeout(doneResizing, 200);
1225        });
1226
1227        function doneResizing() {
1228            for (var i = 0; i < megaNavArray.length; i++) {
1229                (function (i) { megaNavArray[i].element.dispatchEvent(customEvent) })(i);
1230            };
1231        };
1232
1233        (window.requestAnimationFrame) // init mega site nav layout
1234            ? window.requestAnimationFrame(doneResizing)
1235            : doneResizing();
1236    }
1237}());

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.