PageSourceSearch

https://www.bbw-kompetenzzentrum.de/_assets/7b3ba8bb3871137dffa4d9…/JavaScript/bundle.main.js?1791461246

js bbw-kompetenzzentrum.de collected 2026-10-09 23:21:45 UTC 284,515 bytes, 9,677 lines download raw bytes

1class EnhancedCoreRegistry {
2    constructor() {
3        this.pluginDataRegistry = new Map();
4    }
5
6    pluginExists(pluginIdentifier, selectorIdentifier) {
7        if (!selectorIdentifier) {
8            return this.pluginDataRegistry.has(pluginIdentifier);
9        }
10
11        if (!this.pluginDataRegistry.has(pluginIdentifier)) {
12            this.pluginDataRegistry.set(pluginIdentifier, new Map());
13        }
14
15        const pluginConfigurations = this.pluginDataRegistry.get(pluginIdentifier);
16        const hasRegistrations = pluginConfigurations.has('registrations');
17        return hasRegistrations ? pluginConfigurations.get('registrations').has(selectorIdentifier) : false;
18    }
19
20    addPlugin(pluginIdentifier, pluginClass, selectorIdentifier, configurationOptions) {
21        if (!this.pluginExists(pluginIdentifier)) {
22            this.pluginDataRegistry.set(pluginIdentifier, new Map());
23        }
24
25        const pluginConfigurations = this.pluginDataRegistry.get(pluginIdentifier);
26
27        pluginConfigurations.set('class', pluginClass);
28        pluginConfigurations.set('identifier', pluginIdentifier);
29
30        if (!pluginConfigurations.has('registrations')) {
31            pluginConfigurations.set('registrations', new Map());
32        }
33
34        if (!pluginConfigurations.has('instances')) {
35            pluginConfigurations.set('instances', []);
36        }
37
38        const registrationDetails = pluginConfigurations.get('registrations');
39        if (selectorIdentifier) {
40            registrationDetails.set(selectorIdentifier, {selector: selectorIdentifier, options: configurationOptions});
41        }
42
43        return this;
44    }
45
46    getPlugin(pluginIdentifier) {
47        return this.pluginDataRegistry.get(pluginIdentifier);
48    }
49
50    removePlugin(pluginIdentifier, selectorIdentifier) {
51        if (!selectorIdentifier) {
52            return this.pluginDataRegistry.delete(pluginIdentifier);
53        }
54
55        const pluginConfigurations = this.pluginDataRegistry.get(pluginIdentifier);
56        if (!pluginConfigurations) {
57            return false;
58        }
59
60        const registrationDetails = pluginConfigurations.get('registrations');
61        if (!registrationDetails) {
62            return false;
63        }
64
65        registrationDetails.delete(selectorIdentifier);
66
67        return this;
68    }
69
70    clearPlugins() {
71        this.pluginDataRegistry.clear();
72
73        return this;
74    }
75
76    getPluginNamesAndData() {
77        return Array.from(this.pluginDataRegistry).reduce((accumulator, values) => {
78            const [key, value] = values;
79            accumulator[key] = value;
80            return accumulator;
81        }, {});
82    }
83}
84
85class Iterator {
86
87  /**
88   * This callback is displayed as a global member.
89   * @callback ObjectIterateCallback
90   * @param {value} value
91   * @param {key} key
92   */
93
94  /**
95   * Iterates over an object
96   *
97   * @param {Array|Object} source
98   * @param {ObjectIterateCallback} callback
99   *
100   * @returns {*}
101   */
102  static iterate(source, callback) {
103    if (source instanceof Map) {
104      return source.forEach(callback);
105    }
106
107    if (Array.isArray(source)) {
108      return source.forEach(callback);
109    }
110
111    if (source instanceof FormData) {
112      for (let entry of source.entries()) {
113        callback(entry[1], entry[0]);
114      }
115      return;
116    }
117
118    if (source instanceof NodeList) {
119      return source.forEach(callback);
120    }
121
122    if (source instanceof HTMLCollection) {
123      return Array.from(source).forEach(callback);
124    }
125
126    if (source instanceof Object) {
127      return Object.keys(source).forEach(key => {
128        callback(source[key], key);
129      });
130    }
131
132    throw new Error(`The element type ${typeof source} is not iterable!`);
133  }
134}
135
136class CoreConfigRegistry {
137
138    constructor() {
139        this.registry = new Map();
140    }
141
142    setConfig(pluginName, configName, config) {
143        let pluginConfigs = this.registry.get(pluginName) || new Map();
144        pluginConfigs.set(configName, config);
145        this.registry.set(pluginName, pluginConfigs);
146
147        return this;
148    }
149
150    getConfig(pluginName, configName) {
151        const pluginConfigs = this.registry.get(pluginName);
152        if (configName) {
153            if (pluginConfigs && pluginConfigs.has(configName)) {
154                return pluginConfigs.get(configName);
155            }
156            return undefined;
157        }
158
159        return pluginConfigs || new Map();
160    }
161
162    deleteConfig(pluginName, configName) {
163        const pluginConfigs = this.registry.get(pluginName);
164        if (pluginConfigs) {
165            pluginConfigs.delete(configName);
166        }
167
168        return this;
169    }
170}
171
172class CoreConfigManagerSingleton {
173
174    constructor() {
175        this.configRegistry = new CoreConfigRegistry();
176    }
177
178    retrieveConfig(pluginName, configName = null) {
179        return this.configRegistry.getConfig(pluginName, configName);
180    }
181
182    storeConfig(pluginName, configName, config) {
183        return this.configRegistry.setConfig(pluginName, configName, config);
184    }
185
186    deleteConfig(pluginName, configName) {
187        return this.configRegistry.deleteConfig(pluginName, configName);
188    }
189
190    obtainRegistry() {
191        return this.configRegistry;
192    }
193
194}
195
196const CoreConfigManagerInstance = Object.freeze(new CoreConfigManagerSingleton());
197
198class CoreConfigManager {
199
200    static retrieve(pluginName, configName = false) {
201        return CoreConfigManagerInstance.retrieveConfig(pluginName, configName);
202    }
203
204    static store(pluginName, configName, config) {
205        return CoreConfigManagerInstance.storeConfig(pluginName, configName, config);
206    }
207
208    static delete(pluginName, configName) {
209        return CoreConfigManagerInstance.deleteConfig(pluginName, configName);
210    }
211
212    static getRegistry() {
213        return CoreConfigManagerInstance.obtainRegistry();
214    }
215
216}
217
218window.CoreConfigManager = CoreConfigManager;
219
220class PluginControlSingleton {
221
222  constructor() {
223    this.controlRegistry = new EnhancedCoreRegistry();
224  }
225
226  addPlugin(
227    pluginIdentifier, pluginBlueprint, targetSelector = document,
228    settings = {}) {
229    if (this.controlRegistry.pluginExists(pluginIdentifier, targetSelector)) {
230      throw new Error(`Plugin "${pluginIdentifier}" ist bereits registriert.`);
231    }
232
233    return this.controlRegistry.addPlugin(pluginIdentifier, pluginBlueprint,
234      targetSelector, settings);
235  }
236
237  retrievePlugins() {
238    return this.controlRegistry.getPluginNamesAndData();
239  }
240
241  fetchPlugin(pluginIdentifier, enforce = true) {
242    if (!pluginIdentifier) {
243      throw new Error('Es muss ein Plugin-Name übergeben werden!');
244    }
245
246    if (!this.controlRegistry.pluginExists(pluginIdentifier)) {
247      if (enforce) {
248        throw new Error(
249          `Das Plugin "${pluginIdentifier}" ist nicht registriert. Möglicherweise müssen Sie es zuerst registrieren.`);
250      } else {
251        this.controlRegistry.addPlugin(pluginIdentifier);
252      }
253    }
254
255    return this.controlRegistry.getPlugin(pluginIdentifier);
256  }
257
258  fetchPluginInstances(pluginIdentifier) {
259    const plugin = this.fetchPlugin(pluginIdentifier);
260
261    return plugin.get('instances');
262  }
263
264  static _queryElements(targetSelector) {
265    if (targetSelector.startsWith('.')) {
266      const regexEl = /^\.([\w-]+)$/.exec(targetSelector);
267      if (regexEl) {
268        return document.getElementsByClassName(regexEl[1]);
269      }
270    } else if (targetSelector.startsWith('#')) {
271      const regexEl = /^#([\w-]+)$/.exec(targetSelector);
272      if (regexEl) {
273        const el = document.getElementById(regexEl[1]);
274
275        return (el) ? [el] : [];
276      }
277    } else if (/^([\w-]+)$/.exec(targetSelector)) {
278      return document.getElementsByTagName(targetSelector);
279    }
280
281    return document.querySelectorAll(targetSelector);
282  }
283
284  static retrievePluginInstanceFromElement(el, pluginIdentifier) {
285    const instances = PluginControlSingleton.retrievePluginInstancesFromElement(
286      el);
287
288    return instances.get(pluginIdentifier);
289  }
290
291  static _isNode(el) {
292    return el instanceof HTMLElement || el === document || el === window;
293  }
294
295  static retrievePluginInstancesFromElement(el) {
296    if (!PluginControlSingleton._isNode(el)) {
297      throw new Error('Passed element is not an Html element!');
298    }
299
300    el.pluginInstances = el.pluginInstances || new Map();
301
302    return el.pluginInstances;
303  }
304
305  static instantiatePluginOnElement(
306    el, pluginBlueprint, settings, pluginIdentifier) {
307    if (typeof pluginBlueprint !== 'function') {
308      throw new Error('Das übergebene Plugin ist keine Funktion oder Classe.');
309    }
310
311    const instance = PluginController.retrievePluginInstanceFromElement(el,
312      pluginIdentifier);
313    if (!instance) {
314      return new pluginBlueprint(el, settings, pluginIdentifier);
315    }
316
317    return instance._update();
318  }
319
320  activatePlugin(
321    pluginBlueprint, targetSelector, settings, pluginIdentifier = false) {
322    if (PluginControlSingleton._isNode(targetSelector)) {
323      return PluginControlSingleton.instantiatePluginOnElement(targetSelector,
324        pluginBlueprint, settings, pluginIdentifier);
325    }
326
327    if (typeof targetSelector === 'string') {
328      targetSelector = PluginControlSingleton._queryElements(targetSelector);
329    }
330
331    return Iterator.iterate(targetSelector, el => {
332      PluginControlSingleton.instantiatePluginOnElement(el, pluginBlueprint,
333        settings, pluginIdentifier);
334    });
335  }
336
337  initializeAllPlugins() {
338    const failures = [];
339    Iterator.iterate(this.retrievePlugins(),
340      (pluginData, pluginIdentifier) => {
341        if (pluginIdentifier &&
342          this.controlRegistry.pluginExists(pluginIdentifier)) {
343          const plugin = this.controlRegistry.getPlugin(pluginIdentifier);
344          if (plugin.has('registrations')) {
345            Iterator.iterate(plugin.get('registrations'),
346              registration => {
347                try {
348                  this.activatePlugin(plugin.get('class'),
349                    registration.selector, registration.options,
350                    plugin.get('name'));
351                }
352                catch (error) {
353                  failures.push(error);
354                }
355              });
356          }
357        }
358      });
359
360    failures.forEach((error) => console.error(error));
361  }
362
363  deepMerge(...objects) {
364    return objects.reduce((prev, obj) => {
365      Object.keys(obj).forEach(key => {
366        const pVal = prev[key];
367        const oVal = obj[key];
368
369        if (Array.isArray(pVal) && Array.isArray(oVal)) {
370          prev[key] = pVal.concat(...oVal);
371        } else {
372          prev[key] = oVal;
373        }
374      });
375      return prev;
376    }, {});
377  }
378
379  _isNode(el) {
380    return el instanceof HTMLElement || el === document || el === window;
381  }
382
383  initializePlugin(pluginIdentifier, selector, options) {
384    let plugin;
385    let pluginClass;
386    let mergedOptions;
387
388    if (this.controlRegistry.pluginExists(pluginIdentifier, selector)) {
389      plugin = this.controlRegistry.getPlugin(pluginIdentifier);
390      const registrationOptions = plugin.get('registrations').get(selector);
391      pluginClass = plugin.get('class');
392      mergedOptions = this.deepMerge(pluginClass.options || {},
393        this.deepMerge(registrationOptions.options || {}, options || {}));
394    } else {
395      plugin = this.controlRegistry.getPlugin(pluginIdentifier);
396      pluginClass = plugin.get('class');
397      mergedOptions = this.deepMerge(pluginClass.options || {}, options || {});
398    }
399
400    this._initializePlugin(pluginClass, selector, mergedOptions,
401      plugin.get('name'));
402  }
403
404  _initializePlugin(pluginClass, selector, options, pluginName = false) {
405    if (this._isNode(selector)) {
406      return PluginControlSingleton._initializePluginOnElement(selector,
407        pluginClass, options, pluginName);
408    }
409
410    if (typeof selector === 'string') {
411      selector = PluginControlSingleton._queryElements(selector);
412    }
413
414    return Iterator.iterate(selector, el => {
415      PluginControlSingleton._initializePluginOnElement(el, pluginClass,
416        options, pluginName);
417    });
418  }
419
420  static _initializePluginOnElement(el, pluginClass, options, pluginName) {
421    if (typeof pluginClass !== 'function') {
422      throw new Error('The passed plugin is not a function or a class.');
423    }
424
425    const instance = PluginController.retrievePluginInstanceFromElement(el,
426      pluginName);
427    if (!instance) {
428      return new pluginClass(el, options, pluginName);
429    }
430
431    return instance._update();
432  }
433
434  static getPluginInstanceFromElement(el, pluginName) {
435    const instances = PluginControlSingleton.getPluginInstancesFromElement(el);
436
437    return instances.get(pluginName);
438  }
439
440  static getPluginInstancesFromElement(el) {
441    if (!this._isNode(el)) {
442      throw new Error('Passed element is not an Html element!');
443    }
444
445    el.__plugins = el.__plugins || new Map();
446
447    return el.__plugins;
448  }
449}
450
451const PluginControlInstance = Object.freeze(
452  new PluginControlSingleton());
453
454class PluginController {
455
456  constructor() {
457    window.PluginController = this;
458  }
459
460  static addPlugin(
461    pluginIdentifier, pluginBlueprint, targetSelector = document,
462    settings = {}) {
463    return PluginControlInstance.addPlugin(pluginIdentifier, pluginBlueprint,
464      targetSelector,
465      settings);
466  }
467
468  static retrievePluginList() {
469    return PluginControlInstance.retrievePlugins();
470  }
471
472  static fetchPlugin(pluginIdentifier) {
473    return PluginControlInstance.fetchPlugin(pluginIdentifier);
474  }
475
476  static retrievePluginInstances(pluginIdentifier) {
477    return PluginControlInstance.fetchPluginInstances(pluginIdentifier);
478  }
479
480  static retrievePluginInstanceFromElement(el, pluginIdentifier) {
481    return PluginControlSingleton.retrievePluginInstanceFromElement(el,
482      pluginIdentifier);
483  }
484
485  static retrievePluginInstancesFromElement(el) {
486    return PluginControlSingleton.retrievePluginInstancesFromElement(el);
487  }
488
489  static initializeAllPlugins() {
490    PluginControlInstance.initializeAllPlugins();
491  }
492
493  static initializePlugin(pluginIdentifier, targetSelector, settings) {
494    PluginControlInstance.initializePlugin(pluginIdentifier, targetSelector,
495      settings);
496  }
497}
498
499window.PluginController = PluginController;
500
501class Core {
502    /**
503     * @param {HTMLElement} el
504     * @param {Object} options
505     * @param {string} pluginName
506     */
507    constructor(el, options = {}, pluginName = false) {
508        if (!this._isNode(el)) {
509            throw new Error('Es ist kein gültiges Element angegeben');
510        }
511
512        this.el = el;
513        this._pluginName = pluginName || this.constructor.name;
514        this.options = this._mergeOptions(options);
515        this._initialized = false;
516
517        this._registerInstance();
518        this._init();
519    }
520
521    init() {
522        throw new Error(`Die "init" Methode für die Klasse "${this._pluginName}" ist nicht definiert.`);
523    }
524
525    _isNode(el) {
526        return el instanceof HTMLElement || el === document || el === window;
527    }
528
529    _init() {
530        if (!this._initialized) {
531            this.init();
532            this._initialized = true;
533        }
534    }
535
536    _hasAttribute(element, attribute) {
537        if (!this._isNode(element)) {
538            throw new Error('Es ist kein gültiges Element angegeben');
539        }
540
541        if (typeof element.hasAttribute !== 'function') {
542            return false;
543        }
544
545        return element.hasAttribute(attribute);
546    }
547
548    _getAttribute(element, attribute, strict = true) {
549        if (strict && this._hasAttribute(element, attribute) === false) {
550            throw new Error(`Das benötigte "${attribute}" existiert nicht!`);
551        }
552
553        if (typeof element.getAttribute !== 'function') {
554            if (strict) {
555                throw new Error(
556                    'Diese Node unterstützt nicht die getAttribute Funktion!');
557            }
558            return undefined;
559        }
560        return element.getAttribute(attribute);
561    }
562
563    _parseAnalyze(value) {
564        try {
565            value = value.replace(',', '.');
566            return JSON.parse(value);
567        }
568        catch {
569            return value.toString();
570        }
571    }
572
573    _stringHelper(string, separator = '') {
574        return string.split(separator).map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join('').replace(/^./,
575            match => match.toLowerCase());
576    }
577
578    _getDataAttribute(element, key, strict = true) {
579        const parsedKey = this._stringHelper(key.replace(/^data-/, ''), '-');
580        if (!this._isNode(element)) {
581            if (strict) throw new Error('Es ist kein gültiges Element angegeben!');
582            return undefined;
583        }
584
585        if (typeof element.dataset === 'undefined') {
586            if (strict) {
587                throw new Error('Diese Node unterstützt kein dataset attribute!');
588            }
589            return undefined;
590        }
591
592        const attribute = element.dataset[parsedKey];
593        if (typeof attribute === 'undefined') {
594            if (strict) {
595                throw new Error(`Das benötigte Attribut "${key}" existiert nicht am Element: ${element}!`);
596            }
597            return attribute;
598        }
599
600        return this._parseAnalyze(attribute);
601    }
602
603    static deepMerge(...objects) {
604        return objects.reduce((prev, obj) => {
605            Object.keys(obj).forEach(key => {
606                const pVal = prev[key];
607                const oVal = obj[key];
608
609                if (Array.isArray(pVal) && Array.isArray(oVal)) {
610                    prev[key] = pVal.concat(...oVal);
611                } else if (typeof pVal === 'object' && typeof oVal === 'object') {
612                    prev[key] = Core.deepMerge(pVal, oVal);
613                } else {
614                    prev[key] = oVal;
615                }
616            });
617            return prev;
618        }, {});
619    }
620
621    _mergeOptions(options) {
622        const dashedPluginName = this._pluginName.replace(/([A-Z])/g, '$1').toLowerCase();
623        const dataAttributeConfig = this._getDataAttribute(this.el, `data-${dashedPluginName}-config`, false);
624        const dataAttributeOptions = this._getAttribute(this.el, `data-${dashedPluginName}-options`, false);
625
626        let configOptions = {};
627        if (dataAttributeConfig) {
628            const configFromManager = CoreConfigManager.retrieve(this._pluginName, dataAttributeConfig);
629            if (typeof configFromManager === 'object' && configFromManager !== null && !Array.isArray(configFromManager)) {
630                configOptions = configFromManager;
631            }
632        }
633
634        let parsedDataAttributeOptions = {};
635        if (dataAttributeOptions) {
636            try {
637                const parsed = JSON.parse(dataAttributeOptions);
638                if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
639                    parsedDataAttributeOptions = parsed;
640                }
641            }
642            catch (e) {
643                console.error(`Fehler beim Parsen von data-${dashedPluginName}-options: ${e}`);
644                throw e;
645            }
646        }
647
648        const mergeableOptions = [this.constructor.options, this.options, options, configOptions, parsedDataAttributeOptions].map(option => {
649            return (typeof option === 'object' && option !== null && !Array.isArray(option)) ? option : {};
650        });
651
652        // Verwenden von Core.deepMerge, um alle validen Objekte zu vereinen
653        return Core.deepMerge(...mergeableOptions);
654    }
655
656    _registerInstance() {
657        const elementPluginInstances = PluginController.retrievePluginInstancesFromElement(this.el);
658        elementPluginInstances.set(this._pluginName, this);
659
660        const plugin = PluginController.fetchPlugin(this._pluginName, false);
661        if (plugin && plugin.get('instances')) {
662            plugin.get('instances').push(this);
663        } else {
664            console.warn(`Plugin ${this._pluginName} ist nicht im PluginController registriert.`);
665        }
666    }
667
668    update() {
669
670    }
671
672    _update() {
673        if (!this._initialized) {
674            return;
675        }
676
677        this.update();
678    }
679}
680
681class TranslatedLang {
682  /**
683   * Extracts translations from window.TYPO3.lang and stores them
684   * under a freely chosen object key (default: "csConfig").
685   *
686   * @param {string}  configKey   Prefix of the language keys, e.g. "splide"
687   * @param {string}  objectName  Name of the returned object key, default "csConfig"
688   * @returns {Object}            e.g. { config: { arrowNext: "Weiter", … } }
689   */
690  static translatedLangConfig(configKey = '', objectName = 'csConfig') {
691    // Punkt automatisch anhängen, damit "splide" → "splide."
692    const prefix = configKey.endsWith('.') ? configKey : `${configKey}.`;
693
694    const src = window?.TYPO3?.lang ?? {};
695    const translations = {};
696
697    for (const [key, value] of Object.entries(src)) {
698      if (key.startsWith(prefix)) {
699        const prop = key.slice(prefix.length); // Präfix + Punkt abschneiden
700        translations[prop] = value;
701      }
702    }
703
704    return { [objectName]: translations };
705  }
706}
707
708class CsPhoneLinks extends Core {
709
710  static options = {
711    globalWrapperSelector: '.js-global-wrapper'
712  };
713
714  /**
715   * Formats any given phone number into the format "0 1 2 3 4 5 6" while preserving the leading "+"
716   * @param {string} phoneNumber - The phone number to format
717   * @returns {string} - Formatted phone number
718   *
719   * Examples:
720   *   +49 123-456-7890   -> + 4 9 1 2 3 4 5 6 7 8 9 0
721   *   (0123) 456-789     -> 0 1 2 3 4 5 6 7 8 9
722   *   00491234567890     -> 0 0 4 9 1 2 3 4 5 6 7 8 9 0
723   *   123456789          -> 1 2 3 4 5 6 7 8 9
724   */
725  formatPhoneNumber(phoneNumber) {
726    // Remove all non-numeric characters except "+"
727    const cleanedNumber = phoneNumber.replace(/(?!^\+)[^0-9]/g, '');
728
729    // Add a space between each digit
730    return cleanedNumber.split('').join(' ');
731  }
732
733  /**
734   * Adds ARIA labels to phone links for accessibility
735   */
736  initPhoneLinks() {
737    const base = this;
738
739    if (!base.el.hasAttribute('aria-label')) { // Skip if already labeled
740      const rawPhoneNumber = base.el.getAttribute('href').replace('tel:', '');
741      if (rawPhoneNumber) { // Ensures the phone number is valid
742        const translatedStringObject = TranslatedLang.translatedLangConfig('links', 'postFixString');
743        const ariaPrefixText = translatedStringObject.postFixString.phone;
744
745        const formattedPhoneNumber = this.formatPhoneNumber(rawPhoneNumber);
746        base.el.setAttribute('aria-label', `${ariaPrefixText} ${formattedPhoneNumber}`);
747      }
748    }
749  }
750
751  init() {
752    this.initPhoneLinks();
753  }
754}
755
756class CsExternalLinks extends Core {
757
758  static options = {
759    classNameForOnlyScreenreaderText: 'h-visible-only-for-screenreader',
760    globalWrapperSelector: '.js-global-wrapper'
761  };
762
763  /**
764   * Appends a screenreader-only text to an external link if not already present
765   */
766  initExternalLinks() {
767    const base = this;
768    if (!base.el.querySelector('.' + base.options.classNameForOnlyScreenreaderText)) { // Check if span already exists
769      const screenReaderText = document.createElement('span');
770      screenReaderText.className = base.options.classNameForOnlyScreenreaderText;
771
772      const translatedStringObject = TranslatedLang.translatedLangConfig('links', 'postFixString');
773      const postfixText = translatedStringObject.postFixString.external;
774
775      screenReaderText.textContent = ` (${postfixText})`;
776      base.el.appendChild(screenReaderText);
777    }
778  }
779
780  init() {
781    this.initExternalLinks();
782  }
783}
784
785class CsScrollbar extends Core {
786
787  /**
788   * recalculates and sets css variable
789   */
790  static setScrollbarScreenWidth() {
791    const scrollbarWidth = 2 * Math.round((window.innerWidth - document.body.clientWidth) / 2);
792
793    document.documentElement.style.setProperty('--scrollbar-width', scrollbarWidth + 'px');
794  }
795
796  init() {
797    CsScrollbar.setScrollbarScreenWidth();
798
799    window.addEventListener('resize', function() {
800      CsScrollbar.setScrollbarScreenWidth();
801    });
802  }
803}
804
805class Aria {
806  /**
807   * Sets the `aria-expanded` attribute on the given element.
808   *
809   * @param {HTMLElement} el - The DOM element on which to set the attribute.
810   * @param {boolean} [isExpanded=false] - Whether the element is expanded (`true`) or collapsed (`false`).
811   */
812  static setExpanded(el, isExpanded = false) {
813    if (!el) {
814      return;
815    }
816
817    if (isExpanded) {
818      el.setAttribute('aria-expanded', 'true');
819    } else {
820      el.setAttribute('aria-expanded', 'false');
821    }
822  }
823}
824
825class Focus {
826  /**
827   * Sets focus to the given element after an optional delay.
828   *
829   * @param {HTMLElement} el - The element to focus.
830   * @param {number} [timeout=1] - Delay in milliseconds before setting focus.
831   */
832  static setFocusToElement(el, timeout = 1) {
833    if (!el) {
834      return;
835    }
836
837    setTimeout(() => {
838      el.focus();
839    }, timeout);
840  }
841
842  /**
843   * Finds the next focusable element in the document, optionally skipping elements within
844   * a given container or restricting focus to a parent element.
845   *
846   * Useful for handling custom keyboard navigation (e.g. in modals or menus).
847   *
848   * @param {HTMLElement|null} [elementWhichChildrenAreExcluded=null] - If set, all focusable children inside this element will be excluded from navigation.
849   * @param {boolean} [forward=true] - Direction of navigation: `true` for forward (Tab), `false` for backward (Shift+Tab).
850   * @param {HTMLElement|null} [activeElementParam=null] - Optional override for the currently active element (defaults to `document.activeElement`).
851   * @param {HTMLElement|null} [allowedParentElement=null] - If set, only elements within this container are considered focusable.
852   * @returns {HTMLElement|null} The next focusable element or `null` if none is found.
853   */
854  static getNextFocusableElement(elementWhichChildrenAreExcluded 
854= null, forward = true, activeElementParam = null, allowedParentElement = null) {
855    const activeElement = activeElementParam || document.activeElement;
856
857    const selector = [
858      'a[href]',
859      'button:not([disabled])',
860      'input:not([type="hidden"]):not([disabled])',
861      'select:not([disabled])',
862      'textarea:not([disabled])',
863      '[tabindex]:not([tabindex="-1"])'
864    ].join(',');
865
866    const focusableElements = Array.from(document.querySelectorAll(selector))
867      .filter(node => node.offsetParent !== null);
868
869    let idx = focusableElements.indexOf(activeElement);
870    const step = forward ? 1 : -1;
871    let next = null;
872
873    idx += step;
874    while (idx >= 0 && idx < focusableElements.length) {
875      const candidate = focusableElements[idx];
876
877      // Skip if the current slot is empty (null or undefined)
878      if (!candidate) {
879        idx += step;
880        continue;
881      }
882
883      // Skip if the candidate sits inside a container whose children are excluded
884      if (elementWhichChildrenAreExcluded?.contains(candidate)) {
885        idx += step;
886        continue;
887      }
888
889      // Skip if any ancestor of the candidate has the [inert] attribute
890      if (candidate.closest('[inert]')) {
891        idx += step;
892        continue;
893      }
894
895      // If an "allowed" selector is set, skip everything outside of it
896      if (allowedParentElement && !allowedParentElement.contains(candidate)) {
897        idx += step; continue;
898      }
899
900      // Valid focus target found
901      next = candidate;
902      break;
903    }
904
905    // if no next element left -> focus on document body
906    if (!next) {
907      document.body.tabIndex = -1;
908      Focus.setFocusToElement(document.body);
909      setTimeout(() => document.body.removeAttribute('tabindex'), 10);
910      // leave next on null to prevent other function reactions
911    }
912
913    return next;
914  }
915}
916
917let Scroll$1 = class Scroll {
918
919  /**
920   * Toggles the scrolling behavior of a specified element.
921   * When activated, the element regains its normal scroll behavior.
922   * When deactivated, vertical scrolling is hidden. If the element is the BODY,
923   * additional styles are applied to prevent unintended layout shifts.
924   *
925   * @param {HTMLElement} el - The HTML element to modify scrolling for.
926   * @param {boolean} [activate=true] - Determines whether scrolling should be enabled (true) or disabled (false).
927   */
928  static toggleScrollBehavior(el, activateScrolling = true) {
929    if (!el) {
930      return;
931    }
932
933    if (activateScrolling) {
934      el.style.overflowY = 'visible';
935
936      if (el.tagName === 'BODY') {
937        el.style.position = 'initial';
938        el.style.width = 'initial';
939
940        const scrollY = parseInt(document.body.style.top || '0', 10) * -1;
941        el.style.top = '';
942        window.scrollTo(0, scrollY);
943      }
944    } else {
945      el.style.overflowY = 'hidden';
946
947      if (el.tagName === 'BODY') {
948        el.style.position = 'fixed';
949        el.style.width = '100%';
950
951        const scrollY = window.scrollY;
952        el.style.top = `-${scrollY}px`;
953      }
954    }
955
956    CsScrollbar.setScrollbarScreenWidth();
957  }
958
959  /**
960   * Sets the vertical overflow behavior of the element to 'auto'.
961   *
962   * This allows the element to scroll vertically if its content exceeds its height.
963   *
964   * @param {HTMLElement} el - The element to modify.
965   */
966  static setScrollBehaviorToAuto(el) {
967    if (!el) {
968      return;
969    }
970
971    el.style.overflowY = 'auto';
972
973    CsScrollbar.setScrollbarScreenWidth();
974  }
975};
976
977class Inert {
978  /**
979   * Adds or removes the `inert` attribute on a single element.
980   *
981   * The `inert` attribute makes the element and its children unfocusable
982   * and inaccessible to assistive technologies.
983   *
984   * @param {HTMLElement} el - The element to modify.
985   * @param {boolean} [hideElement=true] - If `true`, adds the `inert` attribute; if `false`, removes it.
986   */
987  static setInertAttr(el, hideElement = true) {
988    if (!el) {
989      return;
990    }
991
992    if (hideElement) {
993      el.setAttribute('inert', '');
994    } else {
995      el.removeAttribute('inert');
996    }
997  }
998
999  /**
1000   * Adds or removes the `inert` attribute on multiple elements.
1001   *
1002   * @param {HTMLElement[]} elements - Array of elements to modify.
1003   * @param {boolean} [hideElements=true] - If `true`, adds the `inert` attribute to each element; if `false`, removes it.
1004   */
1005  static setInertAttrOnElements(elements, hideElements = true) {
1006    elements.forEach(el => Inert.setInertAttr(el, hideElements));
1007  }
1008}
1009
1010class CssProperties {
1011  /**
1012   * Retrieves the value of a CSS property from a specified element.
1013   * If an array of units is provided via `replaceUnits`, the method checks whether the value ends with
1014   * one of these units. If so, the unit is removed and the numeric part is returned as a float.
1015   * If no element is provided, the document root element (document.documentElement) is used by default.
1016   *
1017   * @param {string} name - The name of the CSS property (e.g., '--main-color' or '--container-width').
1018   * @param {string[]} [replaceUnits='px'] - An array of CSS units to remove (e.g., ['px', 'em', 'rem', 'ms', 's']).
1019   * @param {HTMLElement} [element=document.documentElement] - The HTML element from which the CSS property is read.
1020   * @returns {(number|string|null)} - The numeric value if a unit match was found and stripped,
1021   *                                   or the original value as a trimmed string. Returns null if the element or property is not valid.
1022   */
1023  static getCssProperty(name, replaceUnits = ['px'], element = document.documentElement) {
1024    if (!element || !window.getComputedStyle) {
1025      return null;
1026    }
1027
1028    const value = getComputedStyle(element).getPropertyValue(name).trim();
1029
1030    for (const unit of replaceUnits) {
1031      if (value.endsWith(unit)) {
1032        const numeric = parseFloat(value.replace(unit, '').trim());
1033        if (isNaN(numeric)) {
1034          return null;
1035        }
1036
1037        return unit === 's' ? numeric * 1000 : numeric; // Convert seconds to milliseconds
1038      }
1039    }
1040
1041    return value; // Return original trimmed value if no matching unit was found
1042  }
1043
1044
1045  /**
1046   * Retrieves the value of a CSS property (in pixels) from a specified element.
1047   * If the property is defined in pixels (e.g., '20px'), the 'px' unit is stripped
1048   * and the numeric value is returned. If the property has a different unit,
1049   * the original value is returned unchanged. If no element is provided,
1050   * the document root element (document.documentElement) is used by default.
1051   *
1052   *
1053   * @param {string} name - The name of the CSS property (e.g., '--main-color' or 'width').
1054   * @param {HTMLElement} [element=document.documentElement] - The HTML element from which the CSS property is read. Defaults to the document root element.
1055   * @returns {(number|string|null)} - The numeric value if the property is in pixels, or the original value as a string if it is in another unit. Returns null if the element or property is not valid.
1056   */
1057  static getCssPixelProperty(name, element) {
1058    if (!element) {
1059      element = document.documentElement;
1060    }
1061
1062    // Check if the element exists and getComputedStyle is available
1063    if (window.getComputedStyle && element) {
1064      const value = getComputedStyle(element).getPropertyValue(name);
1065      if (value.includes('px')) {
1066        return parseFloat(value.replace('px', '')); // Remove 'px' and convert to a number
1067      }
1068      if (value.includes('ms')) {
1069        return parseFloat(value.replace('ms', '')); // Remove 'ms' and convert to a number
1070      }
1071      return value; // Return the original value if it's not in 'px'
1072    }
1073
1074    return null; // Return null if the element or property name is not valid
1075  }
1076
1077  /**
1078   * Retrieves a CSS time property's value from a specified element.
1079   * If the value ends with 'ms' or 's', the unit is removed and the numeric part is returned as a float.
1080   * If no element is provided, the document root element (document.documentElement) is used by default.
1081   *
1082   * This is a convenience wrapper around `getCssProperty()` with fixed units ['ms', 's'].
1083   *
1084   * @param {string} name - The name of the CSS time property (e.g., '--animation-delay').
1085   * @param {HTMLElement} [element=document.documentElement] - The HTML element from which the CSS property is read.
1086   * @returns {(number|string|null)} - The numeric value (unit stripped) if a known time unit was found,
1087   *                                   the raw value as a string otherwise, or null if invalid.
1088   */
1089  static getCssTimeProperty(name, element = document.documentElement) {
1090    return CssProperties.getCssProperty(name, ['ms', 's'], element);
1091  }
1092}
1093
1094// helper/breakpoint.helper.js
1095
1096class Breakpoint {
1097  /**
1098   * Returns a media query string based on a data attribute that contains a CSS variable.
1099   *
1100   * @param {HTMLElement} element - The element with the data attribute.
1101   * @param {string} dataAttr - The data attribute name (e.g. 'data-breakpoint-max').
1102   * @returns {string|null} The constructed media query string or null if attribute is missing.
1103   */
1104  static getMediaQueryFromDataAttr(element, dataAttr) {
1105    const value = element.getAttribute(dataAttr);
1106    if (!value) return null;
1107
1108    const px = Number(CssProperties.getCssPixelProperty(value));
1109    if (dataAttr.includes('max')) {
1110      return `(max-width: ${px}px)`;
1111    }
1112    if (dataAttr.includes('min')) {
1113      return `(min-width: ${px + 1}px)`;
1114    }
1115
1116    return `(max-width: ${px}px)`; // fallback
1117  }
1118
1119  /**
1120   * Returns a media query string based on a CSS custom property (e.g. '--breakpoint-mobile').
1121   *
1122   * @param {string} cssVar - The CSS variable name (e.g. '--breakpoint-mobile').
1123   * @returns {string} The constructed media query string.
1124   */
1125  static getMediaQueryFromCssVar(cssVar) {
1126    const px = Number(CssProperties.getCssPixelProperty(cssVar));
1127    return `(max-width: ${px}px)`;
1128  }
1129
1130  /**
1131   * Checks if the current viewport matches the media query defined via a data attribute.
1132   *
1133   * @param {HTMLElement} element - The element with the data attribute.
1134   * @param {string} dataAttr - The name of the data attribute.
1135   * @returns {boolean} True if the media query matches, or no attribute is present.
1136   */
1137  static matches(element, dataAttr) {
1138    if (!element) return true;
1139    const query = this.getMediaQueryFromDataAttr(element, dataAttr);
1140    if (!query) return true; // no attr means "always matches"
1141    return window.matchMedia(query).matches;
1142  }
1143
1144  /**
1145   * Checks if the current viewport is considered "mobile" based on a CSS variable.
1146   *
1147   * @param {string} cssVar - The CSS variable name (e.g. '--breakpoint-mobile').
1148   * @returns {boolean} True if the viewport width is smaller than or equal to the breakpoint.
1149   */
1150  static isMobileByCssVar(cssVar) {
1151    return window.matchMedia(this.getMediaQueryFromCssVar(cssVar)).matches;
1152  }
1153
1154  /**
1155   * Checks if the current viewport is considered "desktop" based on a CSS variable.
1156   *
1157   * @param {string} cssVar - The CSS variable name (e.g. '--breakpoint-mobile').
1158   * @returns {boolean} True if the viewport width is larger than the breakpoint.
1159   */
1160  static isDesktopByCssVar(cssVar) {
1161    return !this.isMobileByCssVar(cssVar);
1162  }
1163}
1164
1165class CsModalManager extends Core {
1166  static options = {
1167    triggerDataAttribute: 'data-cs-modal-trigger',
1168    triggerLabelOpen: 'data-cs-modal-trigger-label-open',
1169    triggerLabelClose: 'data-cs-modal-trigger-label-close',
1170    triggerActiveClass: 'is-opened',
1171    modalDataAttribute: 'data-cs-modal',
1172    activeClass: 'is-active',
1173    modalInitiallyOpenDataAttribute: 'data-cs-modal-initial-open',
1174    modalMaxBreakpointDataAttribute: 'data-cs-modal-max-breakpoint',
1175    modalMinBreakpointDataAttribute: 'data-cs-modal-min-breakpoint',
1176    modalBreakpointLeaveInertDataAttribute: 'data-cs-modal-breakpoint-leave-inert',
1177    modalContentWrapperSelector: '.js-cs-modal-content-wrapper',
1178    dimmingAreaDataAttribute: 'data-cs-modal-dimming-area',
1179    hiddenAreasDesktopDataAttribute: 'data-cs-modal-hidden-areas-desktop',
1180    hiddenAreasMobileDataAttribute: 'data-cs-modal-hidden-areas-mobile',
1181    hiddenAreasBreakpointDataAttribute: 'data-cs-modal-hidden-areas-breakpoint',
1182    closeSelector: '.js-cs-modal-close',
1183    modalFocusOnOpenDataAttribute: 'data-cs-modal-focus-on-open-selector',
1184    fallbackFocusSelector: '.js-cs-modal-focus-fallback'
1185  };
1186
1187  /* ------------------------------------------------------------------
1188   * PUBLIC API --------------------------------------------------------
1189   * ---------------------------------------------------------------- */
1190
1191  open(name, setFocus = true, trigger = null) {
1192
1193    const modal = this.findModalByName(name);
1194    if (!modal) return;
1195
1196    if (trigger) {
1197      this.lastOpenTrigger = trigger;
1198    }
1199
1200    // check if modal is deactivated for some breakpoint
1201    if (
1202      !Breakpoint.matches(modal, CsModalManager.options.modalMaxBreakpointDataAttribute) ||
1203      !Breakpoint.matches(modal, CsModalManager.options.modalMinBreakpointDataAttribute)
1204    ) {
1205      return;
1206    }
1207
1208    // close any other open modals
1209    this.close(false);
1210
1211    this.activeModal = modal;
1212    this.activeModalName = name;
1213
1214    CsModalManager.setModalAttributes(modal, true);
1215    this.setTriggerAttributes(name, true);
1216
1217    // set hidden areas to inert
1218    this.setHiddenAreas(modal, true);
1219
1220    // set focus to modal close button
1221    if (setFocus) {
1222      let focusElement = null;
1223      // focus to individual element via data attribute
1224      const focusSelector = modal.getAttribute(
1225        this.options.modalFocusOnOpenDataAttribute
1226      );
1227      if (focusSelector) {
1228        focusElement = modal.querySelector(focusSelector);
1229      }
1230      // fallback for focus: close button
1231      if (!focusElement) {
1232        const closeSelectorClass = this.options.closeSelector.replace('.', '');
1233        focusElement = modal.getElementsByClassName(closeSelectorClass)[0];
1234      }
1235      if (focusElement) {
1236        Focus.setFocusToElement(focusElement);
1237      } else {
1238        console.warn(
1239          `CsModalManager: kein Fokus-Element für Modal "${name}" gefunden.`
1240        );
1241      }
1242    }
1243
1244    // add listener for background click once per modal instance
1245    const contentWrapper = modal.querySelector(this.options.modalContentWrapperSelector);
1246    if (contentWrapper && !modal.csModalBackgroundBound) {
1247      modal.addEventListener('click', this.handleBackgroundClick);
1248      modal.csModalBackgroundBound = true;
1249    }
1250
1251    // prevent body from scrolling
1252    Scroll$1.toggleScrollBehavior(this.body, false);
1253
1254    // add individual dimming areas
1255    this.bindDimmingArea(modal);
1256
1257    this.dispatchModalEvent('open', modal, name);
1258  }
1259
1260  close(returnFocusToOpenTrigger = true, closeTrigger = null) {
1261
1262    if (!this.activeModal) return;
1263
1264    // activate body scrolling
1265    Scroll$1.toggleScrollBehavior(this.body);
1266
1267    CsModalManager.setModalAttributes(this.activeModal, false);
1268    this.setTriggerAttributes(this.activeModalName, false);
1269
1270    // remove inert from hidden areas
1271    this.setHiddenAreas(this.activeModal, false);
1272
1273    // remove individual dimming areas
1274    this.unbindDimmingArea();
1275
1276    // return focus to its trigger element
1277    if (returnFocusToOpenTrigger) {
1278      const openTriggerElement = this.lastOpenTrigger;
1279      if (openTriggerElement) {
1280        Focus.setFocusToElement(openTriggerElement);
1281      } else {
1282        const defaultFocusElement = document.querySelector(this.options.fallbackFocusSelector);
1283
1284        if (defaultFocusElement) {
1285          Focus.setFocusToElement(defaultFocusElement);
1286        } else {
1287          console.warn(
1288            `CsModalManager: weder Trigger noch Fallback (${this.options.fallbackFocusSelector}) sichtbar.`
1289          );
1290        }
1291      }
1292    }
1293
1294    // remove background click listener if it was attached
1295    if (this.activeModal.csModalBackgroundBound) {
1296      this.activeModal.removeEventListener('click', this.handleBackgroundClick);
1297      delete this.activeModal.csModalBackgroundBound;
1298    }
1299
1300    this.dispatchModalEvent('close', this.activeModal, this.activeModalName, closeTrigger);
1301
1302    this.activeModal = null;
1303    this.activeModalName = null;
1304  }
1305
1306  toggle(name, trigger = null) {
1307    (this.activeModalName === name) ? this.close(true, trigger) : this.open(name, true, trigger);
1308  }
1309
1310  /* ------------------------------------------------------------------
1311   * BINDINGS ----------------------------------------------------------
1312   * ---------------------------------------------------------------- */
1313
1314  bindTriggers() {
1315    this.triggers = Array.from(
1316      this.el.querySelectorAll(`[${this.options.triggerDataAttribute}]`)
1317    );
1318
1319    this.triggers.forEach(trigger => {
1320      this.setInitialTriggerLabel(trigger);
1321
1322      trigger.csModalHandler = (e) => {
1323        e.preventDefault();
1324        const name = trigger.dataset.csModalTrigger;
1325        if (name) this.toggle(name, trigger);
1326      };
1327      trigger.addEventListener('click', trigger.csModalHandler);
1328    });
1329  }
1330
1331  bindCloseSelectors(container = this.el) {
1332    this.closeSelectors = Array.from(
1333      container.querySelectorAll(this.options.closeSelector)
1334    );
1335
1336    this.closeSelectors.forEach(closeSelector => {
1337      if (closeSelector.csModalHandler) return; // prevent double binding
1338
1339      closeSelector.csModalHandler = (e) => {
1340        e.preventDefault();
1341        this.close(true, closeSelector);
1342      };
1343
1344      closeSelector.addEventListener('click', closeSelector.csModalHandler);
1345    });
1346  }
1347
1348  /* ------------------------------------------------------------------
1349   * SETUP -------------------------------------------------------------
1350   * ---------------------------------------------------------------- */
1351
1352  setInitialModalAttributes() {
1353    this.modals = this.getAllModals();
1354
1355    this.modals.forEach(modal => {
1356      const shouldBeInitialized = Breakpoint.matches(modal, CsModalManager.options.modalMaxBreakpointDataAttribute) && Breakpoint.matches(modal, CsModalManager.options.modalMinBreakpointDataAttribute);
1357
1358      if (shouldBeInitialized) {
1359        // initially set modal to inert
1360        Inert.setInertAttr(modal);
1361      }
1362    });
1363  }
1364
1365  openModalOnInit() {
1366    // check for initially open modal
1367    const modals = this.getAllModals();
1368    const initialOpenModals = modals.filter(modal =>
1369      modal.hasAttribute(CsModalManager.options.modalInitiallyOpenDataAttribute)
1370    );
1371
1372    if (initialOpenModals.length > 1) {
1373      console.warn(
1374        `CsModalManager: Mehr als ein Modal hat das Attribut "${CsModalManager.options.modalInitiallyOpenDataAttribute}". Es wird nur das erste geöffnet.`);
1375    }
1376
1377    if (initialOpenModals.length == 1) {
1378      const firstModal = initialOpenModals[0];
1379      const name = firstModal.getAttribute(this.options.modalDataAttribute);
1380      if (name) {
1381        setTimeout(() => this.open(name), 0);
1382      }
1383    }
1384  }
1385
1386  /* ------------------------------------------------------------------
1387   * ATTRIBUTE HELPERS -------------------------------------------------
1388   * ---------------------------------------------------------------- */
1389
1390  static setModalAttributes(modal, shouldBeOpened) {
1391    if (shouldBeOpened) {
1392      modal.classList.add(CsModalManager.options.activeClass);
1393      Inert.setInertAttr(modal, false);
1394    } else {
1395      modal.classList.remove(CsModalManager.options.activeClass);
1396      Inert.setInertAttr(modal);
1397    }
1398  }
1399
1400  static destroyModalAttributes(modal) {
1401    modal.classList.remove(CsModalManager.options.activeClass);
1402    if (modal.getAttribute(this.options.modalBreakpointLeaveInertDataAttribute) != '1') {
1403      Inert.setInertAttr(modal, false);
1404    }
1405  }
1406
1407  setInitialTriggerLabel(trigger) {
1408    const openLabel = trigger.getAttribute(this.options.triggerLabelOpen);
1409    if (openLabel) this.setLabelAttributes(trigger, openLabel);
1410  }
1411
1412  setTriggerAttributes(name, shouldBeOpened) {
1413    const triggers = this.triggers.filter(t => t.dataset.csModalTrigger === name);
1414    if (triggers.length === 0) return;
1415
1416    triggers.forEach(trigger => {
1417      trigger.classList.toggle(this.options.triggerActiveClass, shouldBeOpened);
1418
1419      if (trigger.hasAttribute('aria-expanded')) {
1420        Aria.setExpanded(trigger, shouldBeOpened);
1421      }
1422
1423      const openLabel = trigger.getAttribute(this.options.triggerLabelOpen);
1424      const closeLabel = trigger.getAttribute(this.options.triggerLabelClose);
1425      if (!openLabel || !closeLabel) return;
1426      this.setLabelAttributes(trigger, shouldBeOpened ? closeLabel : openLabel);
1427    });
1428  }
1429
1430  setLabelAttributes(trigger, label) {
1431    trigger.setAttribute('aria-label', label);
1432    trigger.setAttribute('title', label);
1433  }
1434
1435  /* ------------------------------------------------------------------
1436   * HIDDEN AREA HANDLING ----------------------------------------------
1437   * ---------------------------------------------------------------- */
1438
1439  setHiddenAreas(modal, hideAreas = true) {
1440    // always release previous inerted areas first
1441    if (this.inertedAreas) {
1442      this.inertedAreas.forEach(el => {
1443        // modal, which remains closed, must remain inert
1444        if (!this.isClosedModal(el)) {
1445          Inert.setInertAttr(el, false);
1446        }
1447      });
1448      this.inertedAreas = null;
1449    }
1450
1451    const hiddenAreasValue = this.getHiddenAreasJSON(modal);
1452    if (!hiddenAreasValue) return;
1453
1454    if (!hideAreas) return;
1455
1456    if (hideAreas) {
1457      this.inertedAreas = [];
1458
1459      let hiddenAreaSelectors;
1460      try {
1461        hiddenAreaSelectors = JSON.parse(hiddenAreasValue);
1462      } catch {
1463        return;
1464      }
1465
1466      hiddenAreaSelectors.forEach(hiddenAreaSelector => {
1467        document.querySelectorAll(hiddenAreaSelector).forEach(el => {
1468          Inert.setInertAttr(el);
1469          this.inertedAreas.push(el);
1470        });
1471      });
1472    }
1473  }
1474
1475  getHiddenAreasJSON(modal) {
1476    const hiddenAreasValue = this.isMobile(modal)
1477      ? this.options.hiddenAreasMobileDataAttribute
1478      : this.options.hiddenAreasDesktopDataAttribute;
1479
1480    return modal.getAttribute(hiddenAreasValue);
1481  }
1482
1483  /* ---------------------------------------------------------------
1484 * DIMMING-AREA HANDLING
1485 * ------------------------------------------------------------- */
1486
1487  bindDimmingArea(modal) {
1488    this.unbindDimmingArea();
1489
1490    const selector = modal.getAttribute(this.options.dimmingAreaDataAttribute);
1491    if (!selector) return;
1492
1493    const el = document.querySelector(selector);
1494    if (!el) {
1495      console.warn(`CsModalManager: dimming element "${selector}" not gefunden.`);
1496      return;
1497    }
1498
1499    const handler = () => this.close(false, el);
1500    el.addEventListener('click', handler);
1501
1502    el.classList.add(this.options.activeClass);
1503
1504    this.dimmingAreaEl = el;
1505    this.dimmingAreaEl.csModalDimmingHandler = handler;
1506  }
1507
1508  unbindDimmingArea() {
1509    if (!this.dimmingAreaEl) return;
1510
1511    this.dimmingAreaEl.removeEventListener(
1512      'click',
1513      this.dimmingAreaEl.csModalDimmingHandler
1514    );
1515    delete this.dimmingAreaEl.csModalDimmingHandler;
1516
1517    this.dimmingAreaEl.classList.remove(this.options.activeClass);
1518    this.dimmingAreaEl = null;
1519  }
1520
1521  /* ------------------------------------------------------------------
1522   * BREAKPOINT UTILS --------------------------------------------------
1523   * ---------------------------------------------------------------- */
1524
1525  isMobile(modal) {
1526    return Breakpoint.matches(modal, this.options.hiddenAreasBreakpointDataAttribute);
1527  }
1528
1529  isClosedModal(modal) {
1530    return modal.hasAttribute(this.options.modalDataAttribute) &&
1531      Breakpoint.matches(modal, this.options.modalMaxBreakpointDataAttribute) &&
1532      Breakpoint.matches(modal, this.options.modalMinBreakpointDataAttribute) &&
1533      !modal.classList.contains(this.options.activeClass);
1534  }
1535
1536  /* ------------------------------------------------------------------
1537   * EVENTS -------------------------------------------------------------
1538   * ---------------------------------------------------------------- */
1539
1540  dispatchModalEvent(type, modal, name, triggerLink = null, goToMode = null) {
1541    const evt = new CustomEvent(`cs-modal-${type}`, {
1542      detail: {name, modal, triggerLink, goToMode},
1543      bubbles: true,
1544      composed: true
1545    });
1546
1547    document.dispatchEvent(evt);
1548  }
1549
1550  /* ------------------------------------------------------------------
1551   * HELPERS -----------------------------------------------------------
1552   * ---------------------------------------------------------------- */
1553
1554  findModalByName(name) {
1555    return this.el.querySelector(`[${this.options.modalDataAttribute}="${name}"]`);
1556  }
1557
1558  static getFirstTriggerLinkFromModalName(modalName) {
1559    return document.querySelectorAll(`[${CsModalManager.options.triggerDataAttribute}="${modalName}"]`)[0];
1560  }
1561
1562  getAllModals() {
1563    return Array.from(
1564      document.querySelectorAll(`[${this.options.modalDataAttribute}]`)
1565    );
1566  }
1567
1568  handleKeydown(e) {
1569    switch (e.key) {
1570      case 'Escape':
1571        if (this.activeModal) {
1572          e.preventDefault();
1573          this.close();
1574        }
1575        break;
1576      case ' ':
1577      case 'Spacebar':
1578      case 'ArrowDown': {
1579        const activeEl = document.activeElement;
1580        const isModalTrigger = activeEl?.hasAttribute(this.options.triggerDataAttribute);
1581
1582        if (isModalTrigger) {
1583          e.preventDefault();
1584          this.dispatchModalEvent('keyevent-on-trigger-to-open-modal', null, null, document.activeElement);
1585        }
1586        break;
1587      }
1588      case 'ArrowLeft':
1589      case 'ArrowRight':
1590        if (this.activeModal) {
1591          e.preventDefault();
1592          this.dispatchModalEvent('keyevent-on-trigger-to-close-modal', this.activeModal, this.activeModalName, document.activeElement, e.key === 'ArrowLeft' ? 'previous' : 'next');
1593        }
1594        break;
1595    }
1596  }
1597
1598  handleBackgroundClick(e) {
1599    const contentWrapper = e.currentTarget.querySelector(this.options.modalContentWrapperSelector);
1600    if (!contentWrapper) return;
1601
1602    if (!contentWrapper.contains(e.target)) {
1603      this.close(true, e.target);
1604    }
1605  }
1606
1607  handleResize() {
1608    const modalsWithBreakpoints = Array.from(
1609      document.querySelectorAll(`[${CsModalManager.options.modalMaxBreakpointDataAttribute}],` + `[${CsModalManager.options.modalMinBreakpointDataAttribute}]`)
1610    );
1611
1612    modalsWithBreakpoints.forEach(modal => {
1613      const inRange =
1614        Breakpoint.matches(modal, CsModalManager.options.modalMaxBreakpointDataAttribute) &&
1615        Breakpoint.matches(modal, CsModalManager.options.modalMinBreakpointDataAttribute);
1616
1617      if (inRange) {
1618        if (modal !== this.activeModal) {
1619          CsModalManager.setModalAttributes(modal, false);
1620        }
1621      } else {
1622        if (this.activeModal === modal) {
1623          this.close(false);
1624        }
1625        CsModalManager.destroyModalAttributes(modal);
1626      }
1627    });
1628
1629    if (this.activeModal) {
1630      this.setHiddenAreas(this.activeModal, true);
1631    }
1632  }
1633
1634  /* ------------------------------------------------------------------
1635   * LIFECYCLE ---------------------------------------------------------
1636   * ---------------------------------------------------------------- */
1637
1638  init() {
1639    this.body = document.body;
1640    this.handleKeydown = this.handleKeydown.bind(this);
1641    this.handleBackgroundClick = this.handleBackgroundClick.bind(this);
1642    this.handleResize = this.handleResize.bind(this);
1643
1644    this.activeModal = null;
1645    this.activeModalName = null;
1646    this.lastOpenTrigger = null;
1647
1648    this.setInitialModalAttributes();
1649    this.bindTriggers();
1650    this.bindCloseSelectors();
1651
1652    document.addEventListener('keydown', this.handleKeydown);
1653    window.addEventListener('resize', this.handleResize);
1654
1655    this.openModalOnInit();
1656  }
1657}
1658
1659const cfg = {
1660  selectors: {
1661    header: '.js-header',
1662    globalWrapper: '.js-global-wrapper',
1663    menuMain: '.js-menu-main',
1664    flyoutContainer: '.js-flyout-container',
1665    flyout: '.js-flyout',
1666    flyoutInner: '.js-flyout-inner',
1667    menuList: '.js-menu-main__list',
1668    menuListHeader: '.js-main-menu-mobile-header',
1669    backButton: '.js-menu-main-mobile-level-up',
1670    menuItem: '.js-menu-main__item',
1671    menuLink: '.js-menu-main__link',
1672    menuListOverview: '.js-menu-main__item--overview',
1673    menuLinkWithChildren: '.js-menu-main__link--has-children',
1674    menuLinkWithChildrenCss: '.menu-main__link--has-children'
1675  },
1676  dataAttrs: {
1677    linkItemUid: 'data-menu-item-uid',
1678    menuLevel: 'data-menu-level',
1679    activeLinkUid: 'data-menu-active-uid'
1680  },
1681  breakpoints: {
1682    mobileMenu: '--breakpoint-mobile-menu'
1683  },
1684  classes: {
1685    active: 'active'},
1686  levelPrefix: '--level-'
1687};
1688
1689class MenuHelper {
1690
1691  /**
1692   * Returns all sibling elements of a given DOM element.
1693   *
1694   * @param {HTMLElement} el - The reference element.
1695   * @returns {HTMLElement[]} Array of sibling elements.
1696   */
1697  static getSiblings(el) {
1698    return Array.from(el.parentElement.children).filter(child => child !== el);
1699  }
1700
1701  /**
1702   * Returns the subnavigation layer (e.g., submenu) for a given link element.
1703   *
1704   * @param {HTMLElement} link - The link element.
1705   * @returns {HTMLElement|null} The next sibling element (submenu), or `null` if none exists.
1706   */
1707  static getSubNavigationLayerFromLink(link) {
1708    return link.nextElementSibling;
1709  }
1710
1711  /**
1712   * Returns the parent navigation layer of a given link.
1713   *
1714   * @param {HTMLElement} link - The link element inside a menu.
1715   * @param {boolean} [useFlyoutOuterContainer=false] - Whether to search for the flyout outer container, otherwise flyout inner container.
1716   * @param {number|null} [level=null] - Optional override for menu level, e.g. if link does not have the data attribute for level.
1717   * @returns {HTMLElement|null} The parent navigation container element.
1718   */
1719  static getParentNavigationLayerFromLink(link, useFlyoutOuterContainer = false, level = null) {
1720    let parentNavigationLayer = null;
1721    let levelValue = level ? level : Number(link.getAttribute(cfg.dataAttrs.menuLevel));
1722
1723    if (levelValue == 1) {
1724      parentNavigationLayer = link.closest(cfg.selectors.menuMain);
1725    } else if (levelValue == 2) {
1726      parentNavigationLayer = link.closest(useFlyoutOuterContainer ? cfg.selectors.flyoutContainer : cfg.selectors.flyoutInner);
1727    } else {
1728      parentNavigationLayer = link.closest(cfg.selectors.menuList);
1729    }
1730    return parentNavigationLayer;
1731  }
1732
1733  /**
1734   * Gets the trigger link (e.g., the menu item that opened a submenu) for a given submenu link.
1735   *
1736   * @param {HTMLElement} subLink - A link inside a submenu.
1737   * @returns {HTMLElement|null} The trigger link element.
1738   */
1739  static getParentTriggerLink(subLink) {
1740    let parentNavigationLayer = this.getParentNavigationLayerFromLink(subLink, true);
1741    return parentNavigationLayer.previousElementSibling;
1742  }
1743
1744  /**
1745   * Resets all expanded ARIA states for submenus starting from a given level.
1746   *
1747   * @param {HTMLElement} flyoutContainer - The container element of the flyout.
1748   * @param {number} startingLevel - The starting menu level.
1749   */
1750  static resetExpandedSubLevels(flyoutContainer, startingLevel) {
1751    let level = startingLevel;
1752
1753    while (true) {
1754      const selector = `${cfg.selectors.menuLinkWithChildren}[aria-expanded="true"][${cfg.dataAttrs.menuLevel}="${level}"]`;
1755      const expandedLinks = flyoutContainer.querySelectorAll(selector);
1756
1757      if (expandedLinks.length === 0) {
1758        break;
1759      }
1760
1761      expandedLinks.forEach(link => {
1762        const subNavigationLayer = this.getSubNavigationLayerFromLink(link);
1763        Aria.setExpanded(link, false);
1764        Inert.setInertAttr(subNavigationLayer);
1765      });
1766
1767      level++;
1768    }
1769  }
1770
1771  /**
1772   * Returns the currently active menu link based on a data attribute in the global wrapper.
1773   *
1774   * @returns {HTMLElement|null} The active menu link element, or `null` if not found.
1775   */
1776  static getActiveMenuLink() {
1777    const wrapper = document.querySelector(cfg.selectors.globalWrapper);
1778    if (!wrapper) return null;
1779
1780    const activeUid = wrapper.getAttribute(cfg.dataAttrs.activeLinkUid);
1781    if (!activeUid) return null;
1782
1783    return document.querySelector(`${cfg.selectors.menuLink}[${cfg.dataAttrs.linkItemUid}="${activeUid}"]`);
1784  }
1785
1786  /**
1787   * Toggles the `inert` attribute for elements in a mobile menu level to manage focus and accessibility.
1788   *
1789   * Affects the clicked link, its sibling menu items, and the associated list header. Additionally,
1790   * it inverts the `inert` state for the corresponding subnavigation layer.
1791   *
1792   * @param {HTMLElement} link - The menu link element that was interacted with.
1793   * @param {boolean} [activate=true] - Whether to activate (`true`) or deactivate (`false`) the `inert` state on surrounding elements.
1794   */
1795  static toggleInertLevelMobile(link, activate = true) {
1796    const parentItem = link.closest(cfg.selectors.menuItem);
1797    const parentItemSiblings = MenuHelper.getSiblings(parentItem);
1798    const parentList = link.closest(cfg.selectors.menuList);
1799    const parentListHeader = parentList.previousElementSibling;
1800
1801    // add or remove inert for:
1802    // clicked link
1803    // list item siblings
1804    // list header
1805    const inertTargets = [
1806      link,
1807      ...parentItemSiblings,
1808      ...(parentListHeader?.matches(cfg.selectors.menuListHeader) ? [parentListHeader] : [])
1809    ];
1810
1811    Inert.setInertAttrOnElements(inertTargets, activate);
1812
1813    let subNavigationLayer = MenuHelper.getSubNavigationLayerFromLink(link);
1814    Inert.setInertAttr(subNavigationLayer, !activate);
1815  }
1816}
1817
1818class CsMainNavigation extends Core {
1819
1820  static options = {
1821    openActiveLevelOnMobile: true,
1822    maxVisibleMenuLevelOnDesktop: 3
1823  };
1824
1825  // ==============================
1826  // Utility Methods
1827  // ==============================
1828
1829  isMobile() {
1830    return Breakpoint.isMobileByCssVar(cfg.breakpoints.mobileMenu);
1831  }
1832
1833  isDesktop() {
1834    return !this.isMobile();
1835  }
1836
1837  resetOnBreakpointSwitch(isDesktop) {
1838    const nowIsDesktop = this.isDesktop();
1839
1840    // fix to prevent mobile navigation flicker
1841    this.applyTransitionFix();
1842
1843    if (nowIsDesktop && !isDesktop) {
1844      // switch from mobile to desktop
1845      this.resetMobileMenu();
1846      this.updateAriaAttributesForMenuLevels();
1847      return true;
1848    }
1849
1850    if (!nowIsDesktop && isDesktop) {
1851      // switch from desktop to mobile
1852      this.resetDesktopSubLevels();
1853      // reset flyout height
1854      const flyoutContainer = this.el.querySelector(`${cfg.selectors.menuLink}[aria-expanded="true"] + ${cfg.selectors.flyoutContainer}`);
1855      if (flyoutContainer) {
1856        this.setFlyoutHeight(flyoutContainer);
1857      }
1858      this.updateAriaAttributesForMenuLevels();
1859      return false;
1860    }
1861
1862    return isDesktop;
1863  }
1864
1865  applyTransitionFix() {
1866    this.el.classList.add(cfg.classes.stopTransition);
1867    window.clearTimeout(this.transitionTimer);
1868
1869    this.transitionTimer = window.setTimeout(() => {
1870      this.el.classList.remove(cfg.classes.stopTransition);
1871      this.transitionTimer = null;
1872    }, 100);
1873  }
1874
1875  // direction 1 => next, -1 => previous
1876  focusSiblingItem(currentLink, level, direction = 1) {
1877    let currentList = currentLink.closest('li')?.parentElement;
1878
1879    if (this.isDesktop() && currentLink.parentElement.matches(cfg.selectors.flyoutInner)) {
1880      // desktop menu, close flyout button
1881      currentList = currentLink.nextElementSibling;
1882    } else if (this.isMobile() && !currentList) {
1883      // mobile menu, first level close button
1884      currentList = currentLink.closest('nav').querySelector('ul');
1885    }
1886
1887    const currentNavLayer = MenuHelper.getParentNavigationLayerFromLink(currentLink, false, level);
1888
1889    let target = Focus.getNextFocusableElement(null, direction > 0, currentLink, currentNavLayer);
1890
1891    if (!target) {
1892      if (this.isDesktop() && level === 2 && direction > 0) {
1893        target = currentList?.previousElementSibling;
1894      } else if (this.isDesktop() && level > 2 && direction > 0) {
1895        target = currentList.firstElementChild.nextElementSibling.querySelector('a, button');
1896      } else if (this.isMobile() && level == 1 && direction > 0) {
1897        target = currentList.previousElementSibling.querySelector('a, button');
1898      } else {
1899        const fallback = direction > 0 ? currentList?.firstElementChild : currentList?.lastElementChild;
1900        target = fallback?.querySelector('a, button');
1901      }
1902    }
1903
1904    Focus.setFocusToElement(target);
1905  }
1906
1907  // ==============================
1908  // Navigation State Control
1909  // ==============================
1910
1911  setActiveLinkToActiveState() {
1912    const activeLink = MenuHelper.getActiveMenuLink();
1913    if (!activeLink) return false;
1914
1915    activeLink.classList.add(cfg.classes.active);
1916    activeLink.setAttribute('aria-current', 'page');
1917
1918    // climb up the DOM and mark parent links
1919    let parentLi = activeLink.closest('li');
1920    while (parentLi) {
1921      const parentUl = parentLi.parentElement;
1922      if (parentUl && parentUl.closest('li')) {
1923        const parentLink = parentUl.closest('li').querySelector('a');
1924        if (parentLink) {
1925          parentLink.classList.add(cfg.classes.active);
1926        }
1927        parentLi = parentUl.closest('li'); // weiter nach oben
1928      } else {
1929        parentLi = null; // oberste Ebene erreicht
1930      }
1931    }
1932  }
1933
1934  resetExpandedLinks(callbackPerLink) {
1935    const expandedLinks = this.el.querySelectorAll(`${cfg.selectors.menuLink}[aria-expanded="true"]`);
1936    expandedLinks.forEach(link => {
1937      if (typeof callbackPerLink === 'function') {
1938        callbackPerLink(link);
1939      } else {
1940        Aria.setExpanded(link, false);
1941      }
1942    });
1943  }
1944
1945  resetMobileMenu() {
1946    this.resetExpandedLinks((link) => this.toggleMobileLevel(link, false));
1947  }
1948
1949  resetDesktopSubLevels() {
1950    this.resetExpandedLinks();
1951  }
1952
1953  setFlyoutHeight(flyoutContainer) {
1954    if (!flyoutContainer) return;
1955
1956    // reset flyout height
1957    const flyout = flyoutContainer.querySelector(cfg.selectors.flyout);
1958    flyoutContainer.removeAttribute('style');
1959    flyout?.removeAttribute('style');
1960
1961    if (!this.isDesktop()) return;
1962
1963    // set flyout and container height
1964    const header = document.querySelector(cfg.selectors.header);
1965    const headerHeight = header?.clientHeight ?? 0;
1966
1967    const flyoutContainerTargetHeight = window.innerHeight - headerHeight;
1968    const flyoutContentTargetHeight = flyoutContainer.scrollHeight;
1969    flyoutContainer.style.height = flyoutContainerTargetHeight + 'px';
1970    if (flyoutContentTargetHeight > flyoutContainerTargetHeight) {
1971      flyout.style.height = flyoutContentTargetHeight + 'px';
1972    }
1973  }
1974
1975  updateAriaAttributesForMenuLevels() {
1976    const linksWithChildren = this.el.querySelectorAll('[data-has-children="true"]');
1977    linksWithChildren.forEach(link => {
1978      const level = Number(link.getAttribute(cfg.dataAttrs.menuLevel));
1979      const isTooDeepOnDesktop = this.isDesktop() && level >= this.options.maxVisibleMenuLevelOnDesktop;
1980
1981      if (isTooDeepOnDesktop) {
1982        link.removeAttribute('aria-expanded');
1983        link.removeAttribute('aria-controls');
1984        link.classList.remove(cfg.selectors.menuLinkWithChildren.slice(1));
1985        link.classList.remove(cfg.selectors.menuLinkWithChildrenCss.slice(1));
1986      } else {
1987        link.setAttribute('aria-expanded', 'false');
1988
1989        const uid = link.getAttribute('data-menu-item-uid');
1990        if (uid) {
1991          link.setAttribute('aria-controls', `main-menu-list-${uid}`);
1992        }
1993
1994        link.classList.add(cfg.selectors.menuLinkWithChildrenCss.slice(1));
1995        link.classList.add(cfg.selectors.menuLinkWithChildren.slice(1));
1996      }
1997    });
1998
1999    const flyoutContainer = this.el.querySelectorAll(cfg.selectors.flyoutContainer);
2000    const subLevelLists = this.el.querySelectorAll(`${cfg.selectors.menuList}:not([${cfg.dataAttrs.menuLevel}="1"]):not([${cfg.dataAttrs.menuLevel}="2"])`);
2001
2002    // add inert for:
2003    // all submenu layers
2004    const inertTargets = [
2005      ...flyoutContainer,
2006      ...subLevelLists
2007    ];
2008
2009    Inert.setInertAttrOnElements(inertTargets);
2010  }
2011
2012  markLinksWithChildrenOnce() {
2013    const links = this.el.querySelectorAll(cfg.selectors.menuLinkWithChildren);
2014    links.forEach(link => {
2015      link.setAttribute('data-has-children', 'true');
2016    });
2017  }
2018
2019
2020  // ==============================
2021  // Flyout & Menu Logic
2022  // ==============================
2023
2024  openMobileMenuOnActiveLevel() {
2025    if (!this.options.openActiveLevelOnMobile || this.isDesktop()) return;
2026
2027    if (this.openActiveLevel()) return;
2028  }
2029
2030  toggleMobileLevel(link, toBeOpened = true) {
2031    let navigationLayerToClose = null;
2032    let nextNavigationLayer = null;
2033
2034    if (toBeOpened) {
2035      nextNavigationLayer = MenuHelper.getSubNavigationLayerFromLink(link);
2036      navigationLayerToClose = MenuHelper.getParentNavigationLayerFromLink(link);
2037    } else {
2038      nextNavigationLayer = MenuHelper.getParentNavigationLayerFromLink(link);
2039      navigationLayerToClose = MenuHelper.getSubNavigationLayerFromLink(link);
2040    }
2041
2042    Scroll$1.toggleScrollBehavior(navigationLayerToClose, false);
2043    Scroll$1.toggleScrollBehavior(nextNavigationLayer, true);
2044
2045    const focusTarget = toBeOpened
2046      ? nextNavigationLayer?.querySelector(cfg.selectors.menuListOverview).nextElementSibling.querySelector(cfg.selectors.menuLink)
2047      : link;
2048    Focus.setFocusToElement(focusTarget);
2049
2050    MenuHelper.toggleInertLevelMobile(link, toBeOpened);
2051
2052    Aria.setExpanded(link, toBeOpened);
2053  }
2054
2055  toggleDesktopSubLevel(link, toBeOpened = true) {
2056    const activeFlyoutContainer = link.closest(cfg.selectors.flyoutContainer);
2057    if (!activeFlyoutContainer) return;
2058
2059    //check if another link is aria-expand=true in level 2
2060    let expandedMenuLink = activeFlyoutContainer.querySelector(`${cfg.selectors.menuLinkWithChildren}[aria-expanded="true"]`);
2061    if (expandedMenuLink) {
2062      const level = Number(link.getAttribute(cfg.dataAttrs.menuLevel));
2063      MenuHelper.resetExpandedSubLevels(activeFlyoutContainer, level);
2064    }
2065
2066    Aria.setExpanded(link, toBeOpened);
2067    const subNavigationLayer = MenuHelper.getSubNavigationLayerFromLink(link);
2068    Inert.setInertAttr(subNavigationLayer, !toBeOpened);
2069
2070    if (toBeOpened) {
2071      const currentLevel = Number(link.getAttribute(cfg.dataAttrs.menuLevel));
2072      const nextLevel = currentLevel + 1;
2073      const firstChildLink = link.nextElementSibling.querySelector(
2074        `${cfg.selectors.menuLink}[${cfg.dataAttrs.menuLevel}="${nextLevel}"]`
2075      );
2076      if (firstChildLink) {
2077        Focus.setFocusToElement(firstChildLink);
2078      }
2079    } else {
2080      Focus.setFocusToElement(link);
2081    }
2082
2083    this.setFlyoutHeight(activeFlyoutContainer);
2084  }
2085
2086  openParentLevelsUpTo(activeLink, targetLevel) {
2087    for (let lvl = 1; lvl < targetLevel; lvl++) {
2088      const selector = `${cfg.selectors.menuItem}${cfg.levelPrefix}${lvl}`;
2089      const parentLink = activeLink.closest(selector)?.querySelector(cfg.selectors.menuLink);
2090      if (parentLink) {
2091        this.toggleMobileLevel(parentLink);
2092      }
2093    }
2094  }
2095
2096  openActiveLevel() {
2097    const activeLink = MenuHelper.getActiveMenuLink();
2098    if (!activeLink) return false;
2099
2100    let levelToOpen = Number(activeLink.getAttribute(cfg.dataAttrs.menuLevel));
2101    // Level 0 is the home screen of the mobile menu, so nothing to do if level < 1
2102    if (levelToOpen < 1) return false;
2103
2104    const hasChildren = activeLink.matches(cfg.selectors.menuLinkWithChildren);
2105
2106    const linkToOpen = hasChildren ? activeLink : activeLink.closest(`${cfg.selectors.menuItem}${cfg.levelPrefix}${levelToOpen - 1}`)?.querySelector(cfg.selectors.menuLink);
2107
2108    if (!linkToOpen) return false;
2109
2110    // open the parent levels too
2111    if (levelToOpen >= 2) {
2112      this.openParentLevelsUpTo(activeLink, levelToOpen);
2113    }
2114
2115    this.toggleMobileLevel(linkToOpen);
2116    return true;
2117  }
2118
2119  // ==============================
2120  // Event Handling
2121  // ==============================
2122
2123  handleClickOnMenuLink() {
2124    this.el.addEventListener('click', e => {
2125      const link = e.target.closest(cfg.selectors.menuLinkWithChildren);
2126      if (!link || !this.el.contains(link)) return;
2127
2128      const level = Number(link.getAttribute(cfg.dataAttrs.menuLevel));
2129      const isDesktop = this.isDesktop();
2130      const isDesktopLevel2 = level === 2;
2131
2132      if (isDesktop && isDesktopLevel2) {
2133        e.preventDefault();
2134        this.toggleDesktopSubLevel(link);
2135        return;
2136      }
2137
2138      if (!isDesktop) {
2139        e.preventDefault();
2140        this.toggleMobileLevel(link);
2141        return;
2142      }
2143    });
2144  }
2145
2146  handleClickOnBackButton() {
2147    this.el.addEventListener('click', (e) => {
2148      const backButton = e.target.closest(cfg.selectors.backButton);
2149      if (!backButton) return;
2150      if (this.isDesktop()) return;
2151
2152      const parentTriggerLink = MenuHelper.getParentTriggerLink(backButton);
2153      this.toggleMobileLevel(parentTriggerLink, false);
2154    });
2155  }
2156
2157  handleModalEvents() {
2158    const onModalOpen = (e) => {
2159      const {modal} = e.detail;
2160
2161      if (modal && modal.contains(this.el)) {
2162        this.openMobileMenuOnActiveLevel();
2163      }
2164      if (modal && modal.matches(cfg.selectors.flyoutContainer)) {
2165        this.setFlyoutHeight(modal);
2166      }
2167    };
2168
2169    const onModalClose = (e) => {
2170      const {modal} = e.detail;
2171
2172      if (modal && modal.contains(this.el)) {
2173        this.resetMobileMenu();
2174      }
2175      if (modal && modal.matches(cfg.selectors.flyoutContainer)) {
2176        this.resetDesktopSubLevels();
2177      }
2178    };
2179
2180    const onKeyEventOnTriggerToOpenModal = (e) => {
2181      if (this.isMobile()) return true;
2182      const {triggerLink} = e.detail;
2183
2184      triggerLink.click();
2185    };
2186
2187    const onKeyEventOnTriggerToCloseModal = (e) => {
2188      if (this.isMobile()) return true;
2189
2190      const {goToMode} = e.detail;
2191      const {triggerLink} = e.detail;
2192      const level = Number(triggerLink.getAttribute(cfg.dataAttrs.menuLevel));
2193      const linkHasChildren = triggerLink.matches(cfg.selectors.menuLinkWithChildren);
2194      let targetTriggerLink = null;
2195
2196      if (goToMode == 'next' && linkHasChildren || goToMode == 'next' && level == 1) return true;
2197      if (goToMode == 'previous' && level != 2)  return true;
2198
2199      const parentItemLevel1 = triggerLink.closest(`${cfg.selectors.menuItem}${cfg.levelPrefix}1`);
2200      const parentLinkLevel1 = parentItemLevel1.querySelector(cfg.selectors.menuLink);
2201
2202      // close active modal
2203      parentLinkLevel1.click();
2204
2205      if (goToMode == 'previous') {
2206        // focus the previous item
2207        targetTriggerLink = parentItemLevel1.previousElementSibling.querySelector(cfg.selectors.menuLink);
2208      } else if (goToMode == 'next') {
2209        // focus the next item
2210        targetTriggerLink = parentItemLevel1.nextElementSibling.querySelector(cfg.selectors.menuLink);
2211      }
2212
2213      if (!targetTriggerLink) {
2214        targetTriggerLink = parentLinkLevel1;
2215      }
2216      Focus.setFocusToElement(targetTriggerLink);
2217    };
2218
2219    document.addEventListener('cs-modal-open', onModalOpen);
2220    document.addEventListener('cs-modal-close', onModalClose);
2221    document.addEventListener('cs-modal-keyevent-on-trigger-to-open-modal', onKeyEventOnTriggerToOpenModal);
2222    document.addEventListener('cs-modal-keyevent-on-trigger-to-close-modal', onKeyEventOnTriggerToCloseModal);
2223  }
2224
2225  handleResize() {
2226    let isDesktop = this.isDesktop();
2227
2228    window.addEventListener('resize', () => {
2229      isDesktop = this.resetOnBreakpointSwitch(isDesktop);
2230    });
2231  }
2232
2233  handleKeydown() {
2234    this.el.addEventListener('keydown', (e) => {
2235      const link = e.target.closest('a, button');
2236      if (!link || !this.el.contains(link)) return;
2237
2238      const levelFallback = this.isMobile() ? 1 : 2;
2239
2240      let level = link.closest('ul') ? Number(link.closest('ul')?.getAttribute(cfg.dataAttrs.menuLevel)) : levelFallback;
2241
2242      // is desktop and event on flyout close button
2243      if (this.isDesktop() && link.parentElement.matches(cfg.selectors.flyoutInner)) {
2244        level = 2;
2245      }
2246
2247      const linkHasChildren = link.matches(cfg.selectors.menuLinkWithChildren);
2248      const isMobile = this.isMobile();
2249      const isDesktop = !isMobile;
2250
2251      switch (e.key) {
2252        case ' ':
2253        case 'Spacebar':
2254          // Desktop Level 1 is handled in CsModalManager
2255          if (level >= 2 && isDesktop || isMobile) {
2256            e.preventDefault();
2257            e.stopPropagation();
2258            link.click();
2259          }
2260          break;
2261        case 'ArrowRight':
2262          if (level === 1 && isDesktop) {
2263            e.preventDefault();
2264            this.focusSiblingItem(link, level, 1);
2265          } else if (level === 2 && !linkHasChildren && isDesktop) {
2266            e.preventDefault();
2267            // will be handled in CsModalManager
2268          } else if (level === 2 && linkHasChildren && isDesktop) {
2269            e.preventDefault();
2270            this.toggleDesktopSubLevel(link);
2271          } else if (level > 2 && isDesktop) {
2272            e.preventDefault();
2273            // will be handled in CsModalManager
2274          } else if (isMobile && linkHasChildren) {
2275            e.preventDefault();
2276            this.toggleMobileLevel(link);
2277          } else if (isMobile && !linkHasChildren) {
2278            // do nothing
2279            e.preventDefault();
2280            e.stopPropagation();
2281          }
2282          break;
2283        case 'ArrowLeft':
2284          if (level === 1 && isDesktop) {
2285            e.preventDefault();
2286            this.focusSiblingItem(link, level, -1);
2287          } else if (level === 3 && isDesktop) {
2288            e.preventDefault();
2289            e.stopPropagation();
2290            const triggerLink = link.closest(cfg.selectors.menuList).previousElementSibling;
2291            this.toggleDesktopSubLevel(triggerLink, false);
2292          } else if (level >= 2 && isMobile) {
2293            e.preventDefault();
2294            e.stopPropagation();
2295            const parentTriggerLink = MenuHelper.getParentTriggerLink(link);
2296            this.toggleMobileLevel(parentTriggerLink, false);
2297          }
2298          break;
2299        case 'ArrowDown':
2300          if (level == 1 && isDesktop) break;
2301
2302          e.preventDefault();
2303          e.stopPropagation();
2304          this.focusSiblingItem(link, level, 1);
2305          break;
2306        case 'ArrowUp':
2307          if (level == 1 && isDesktop) break;
2308
2309          e.preventDefault();
2310          this.focusSiblingItem(link, level, -1);
2311          break;
2312        case 'Escape':
2313          if (level === 3 && isDesktop) {
2314            e.preventDefault();
2315            e.stopPropagation();
2316            const triggerLink = link.closest(cfg.selectors.menuList).previousElementSibling;
2317            this.toggleDesktopSubLevel(triggerLink, false);
2318          } else if (level >= 2 && isMobile) {
2319            e.preventDefault();
2320            e.stopPropagation();
2321            const parentTriggerLink = MenuHelper.getParentTriggerLink(link);
2322            this.toggleMobileLevel(parentTriggerLink, false);
2323          }
2324          break;
2325      }
2326    });
2327  }
2328
2329  // ==============================
2330  // Init
2331  // ==============================
2332
2333  init() {
2334    this.setActiveLinkToActiveState();
2335
2336    this.handleModalEvents();
2337
2338    this.handleClickOnMenuLink();
2339    this.handleClickOnBackButton();
2340
2341    this.handleKeydown();
2342
2343    this.handleResize();
2344
2345    this.markLinksWithChildrenOnce();
2346    this.updateAriaAttributesForMenuLevels();
2347  }
2348}
2349
2350function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
2351
2352function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
2353
2354/*!
2355 * Splide.js
2356 * Version  : 4.1.4
2357 * License  : MIT
2358 * Copyright: 2022 Naotoshi Fujita
2359 */
2360var MEDIA_PREFERS_REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
2361var CREATED = 1;
2362var MOUNTED = 2;
2363var IDLE = 3;
2364var MOVING = 4;
2365var SCROLLING = 5;
2366var DRAGGING = 6;
2367var DESTROYED = 7;
2368var STATES = {
2369  CREATED: CREATED,
2370  MOUNTED: MOUNTED,
2371  IDLE: IDLE,
2372  MOVING: MOVING,
2373  SCROLLING: SCROLLING,
2374  DRAGGING: DRAGGING,
2375  DESTROYED: DESTROYED
2376};
2377
2378function empty(array) {
2379  array.length = 0;
2380}
2381
2382function slice(arrayLike, start, end) {
2383  return Array.prototype.slice.call(arrayLike, start, end);
2384}
2385
2386function apply(func) {
2387  return func.bind.apply(func, [null].concat(slice(arguments, 1)));
2388}
2389
2390var nextTick = setTimeout;
2391
2392var noop = function noop() {};
2393
2394function raf(func) {
2395  return requestAnimationFrame(func);
2396}
2397
2398function typeOf(type, subject) {
2399  return typeof subject === type;
2400}
2401
2402function isObject(subject) {
2403  return !isNull(subject) && typeOf("object", subject);
2404}
2405
2406var isArray = Array.isArray;
2407var isFunction = apply(typeOf, "function");
2408var isString = apply(typeOf, "string");
2409var isUndefined = apply(typeOf, "undefined");
2410
2411function isNull(subject) {
2412  return subject === null;
2413}
2414
2415function isHTMLElement(subject) {
2416  try {
2417    return subject instanceof (subject.ownerDocument.defaultView || window).HTMLElement;
2418  } catch (e) {
2419    return false;
vendor: 4,687 bytes, lines 2420-2640
2420  }
2421}
2422
2423function toArray(value) {
2424  return isArray(value) ? value : [value];
2425}
2426
2427function forEach(values, iteratee) {
2428  toArray(values).forEach(iteratee);
2429}
2430
2431function includes(array, value) {
2432  return array.indexOf(value) > -1;
2433}
2434
2435function push(array, items) {
2436  array.push.apply(array, toArray(items));
2437  return array;
2438}
2439
2440function toggleClass(elm, classes, add) {
2441  if (elm) {
2442    forEach(classes, function (name) {
2443      if (name) {
2444        elm.classList[add ? "add" : "remove"](name);
2445      }
2446    });
2447  }
2448}
2449
2450function addClass(elm, classes) {
2451  toggleClass(elm, isString(classes) ? classes.split(" ") : classes, true);
2452}
2453
2454function append(parent, children) {
2455  forEach(children, parent.appendChild.bind(parent));
2456}
2457
2458function before(nodes, ref) {
2459  forEach(nodes, function (node) {
2460    var parent = (ref || node).parentNode;
2461
2462    if (parent) {
2463      parent.insertBefore(node, ref);
2464    }
2465  });
2466}
2467
2468function matches(elm, selector) {
2469  return isHTMLElement(elm) && (elm["msMatchesSelector"] || elm.matches).call(elm, selector);
2470}
2471
2472function children(parent, selector) {
2473  var children2 = parent ? slice(parent.children) : [];
2474  return selector ? children2.filter(function (child) {
2475    return matches(child, selector);
2476  }) : children2;
2477}
2478
2479function child(parent, selector) {
2480  return selector ? children(parent, selector)[0] : parent.firstElementChild;
2481}
2482
2483var ownKeys = Object.keys;
2484
2485function forOwn(object, iteratee, right) {
2486  if (object) {
2487    (right ? ownKeys(object).reverse() : ownKeys(object)).forEach(function (key) {
2488      key !== "__proto__" && iteratee(object[key], key);
2489    });
2490  }
2491
2492  return object;
2493}
2494
2495function assign$1(object) {
2496  slice(arguments, 1).forEach(function (source) {
2497    forOwn(source, function (value, key) {
2498      object[key] = source[key];
2499    });
2500  });
2501  return object;
2502}
2503
2504function merge(object) {
2505  slice(arguments, 1).forEach(function (source) {
2506    forOwn(source, function (value, key) {
2507      if (isArray(value)) {
2508        object[key] = value.slice();
2509      } else if (isObject(value)) {
2510        object[key] = merge({}, isObject(object[key]) ? object[key] : {}, value);
2511      } else {
2512        object[key] = value;
2513      }
2514    });
2515  });
2516  return object;
2517}
2518
2519function omit(object, keys) {
2520  forEach(keys || ownKeys(object), function (key) {
2521    delete object[key];
2522  });
2523}
2524
2525function removeAttribute(elms, attrs) {
2526  forEach(elms, function (elm) {
2527    forEach(attrs, function (attr) {
2528      elm && elm.removeAttribute(attr);
2529    });
2530  });
2531}
2532
2533function setAttribute(elms, attrs, value) {
2534  if (isObject(attrs)) {
2535    forOwn(attrs, function (value2, name) {
2536      setAttribute(elms, name, value2);
2537    });
2538  } else {
2539    forEach(elms, function (elm) {
2540      isNull(value) || value === "" ? removeAttribute(elm, attrs) : elm.setAttribute(attrs, String(value));
2541    });
2542  }
2543}
2544
2545function create(tag, attrs, parent) {
2546  var elm = document.createElement(tag);
2547
2548  if (attrs) {
2549    isString(attrs) ? addClass(elm, attrs) : setAttribute(elm, attrs);
2550  }
2551
2552  parent && append(parent, elm);
2553  return elm;
2554}
2555
2556function style(elm, prop, value) {
2557  if (isUndefined(value)) {
2558    return getComputedStyle(elm)[prop];
2559  }
2560
2561  if (!isNull(value)) {
2562    elm.style[prop] = "" + value;
2563  }
2564}
2565
2566function display(elm, display2) {
2567  style(elm, "display", display2);
2568}
2569
2570function focus(elm) {
2571  elm["setActive"] && elm["setActive"]() || elm.focus({
2572    preventScroll: true
2573  });
2574}
2575
2576function getAttribute(elm, attr) {
2577  return elm.getAttribute(attr);
2578}
2579
2580function hasClass(elm, className) {
2581  return elm && elm.classList.contains(className);
2582}
2583
2584function rect(target) {
2585  return target.getBoundingClientRect();
2586}
2587
2588function remove(nodes) {
2589  forEach(nodes, function (node) {
2590    if (node && node.parentNode) {
2591      node.parentNode.removeChild(node);
2592    }
2593  });
2594}
2595
2596function parseHtml(html) {
2597  return child(new DOMParser().parseFromString(html, "text/html").body);
2598}
2599
2600function prevent(e, stopPropagation) {
2601  e.preventDefault();
2602
2603  if (stopPropagation) {
2604    e.stopPropagation();
2605    e.stopImmediatePropagation();
2606  }
2607}
2608
2609function query(parent, selector) {
2610  return parent && parent.querySelector(selector);
2611}
2612
2613function queryAll(parent, selector) {
2614  return selector ? slice(parent.querySelectorAll(selector)) : [];
2615}
2616
2617function removeClass(elm, classes) {
2618  toggleClass(elm, classes, false);
2619}
2620
2621function timeOf(e) {
2622  return e.timeStamp;
2623}
2624
2625function unit(value) {
2626  return isString(value) ? value : value ? value + "px" : "";
2627}
2628
2629var PROJECT_CODE = "splide";
2630var DATA_ATTRIBUTE = "data-" + PROJECT_CODE;
2631
2632function assert(condition, message) {
2633  if (!condition) {
2634    throw new Error("[" + PROJECT_CODE + "] " + (message || ""));
2635  }
2636}
2637
2638var min = Math.min,
2639    max = Math.max,
2640    floor = Math.floor,
vendor: 4,681 bytes, lines 2641-2816
2641    ceil = Math.ceil,
2642    abs = Math.abs;
2643
2644function approximatelyEqual(x, y, epsilon) {
2645  return abs(x - y) < epsilon;
2646}
2647
2648function between(number, x, y, exclusive) {
2649  var minimum = min(x, y);
2650  var maximum = max(x, y);
2651  return exclusive ? minimum < number && number < maximum : minimum <= number && number <= maximum;
2652}
2653
2654function clamp(number, x, y) {
2655  var minimum = min(x, y);
2656  var maximum = max(x, y);
2657  return min(max(minimum, number), maximum);
2658}
2659
2660function sign(x) {
2661  return +(x > 0) - +(x < 0);
2662}
2663
2664function format(string, replacements) {
2665  forEach(replacements, function (replacement) {
2666    string = string.replace("%s", "" + replacement);
2667  });
2668  return string;
2669}
2670
2671function pad(number) {
2672  return number < 10 ? "0" + number : "" + number;
2673}
2674
2675var ids = {};
2676
2677function uniqueId(prefix) {
2678  return "" + prefix + pad(ids[prefix] = (ids[prefix] || 0) + 1);
2679}
2680
2681function EventBinder() {
2682  var listeners = [];
2683
2684  function bind(targets, events, callback, options) {
2685    forEachEvent(targets, events, function (target, event, namespace) {
2686      var isEventTarget = ("addEventListener" in target);
2687      var remover = isEventTarget ? target.removeEventListener.bind(target, event, callback, options) : target["removeListener"].bind(target, callback);
2688      isEventTarget ? target.addEventListener(event, callback, options) : target["addListener"](callback);
2689      listeners.push([target, event, namespace, callback, remover]);
2690    });
2691  }
2692
2693  function unbind(targets, events, callback) {
2694    forEachEvent(targets, events, function (target, event, namespace) {
2695      listeners = listeners.filter(function (listener) {
2696        if (listener[0] === target && listener[1] === event && listener[2] === namespace && (!callback || listener[3] === callback)) {
2697          listener[4]();
2698          return false;
2699        }
2700
2701        return true;
2702      });
2703    });
2704  }
2705
2706  function dispatch(target, type, detail) {
2707    var e;
2708    var bubbles = true;
2709
2710    if (typeof CustomEvent === "function") {
2711      e = new CustomEvent(type, {
2712        bubbles: bubbles,
2713        detail: detail
2714      });
2715    } else {
2716      e = document.createEvent("CustomEvent");
2717      e.initCustomEvent(type, bubbles, false, detail);
2718    }
2719
2720    target.dispatchEvent(e);
2721    return e;
2722  }
2723
2724  function forEachEvent(targets, events, iteratee) {
2725    forEach(targets, function (target) {
2726      target && forEach(events, function (events2) {
2727        events2.split(" ").forEach(function (eventNS) {
2728          var fragment = eventNS.split(".");
2729          iteratee(target, fragment[0], fragment[1]);
2730        });
2731      });
2732    });
2733  }
2734
2735  function destroy() {
2736    listeners.forEach(function (data) {
2737      data[4]();
2738    });
2739    empty(listeners);
2740  }
2741
2742  return {
2743    bind: bind,
2744    unbind: unbind,
2745    dispatch: dispatch,
2746    destroy: destroy
2747  };
2748}
2749
2750var EVENT_MOUNTED = "mounted";
2751var EVENT_READY = "ready";
2752var EVENT_MOVE = "move";
2753var EVENT_MOVED = "moved";
2754var EVENT_CLICK = "click";
2755var EVENT_ACTIVE = "active";
2756var EVENT_INACTIVE = "inactive";
2757var EVENT_VISIBLE = "visible";
2758var EVENT_HIDDEN = "hidden";
2759var EVENT_REFRESH = "refresh";
2760var EVENT_UPDATED = "updated";
2761var EVENT_RESIZE = "resize";
2762var EVENT_RESIZED = "resized";
2763var EVENT_DRAG = "drag";
2764var EVENT_DRAGGING = "dragging";
2765var EVENT_DRAGGED = "dragged";
2766var EVENT_SCROLL = "scroll";
2767var EVENT_SCROLLED = "scrolled";
2768var EVENT_OVERFLOW = "overflow";
2769var EVENT_DESTROY = "destroy";
2770var EVENT_ARROWS_MOUNTED = "arrows:mounted";
2771var EVENT_ARROWS_UPDATED = "arrows:updated";
2772var EVENT_PAGINATION_MOUNTED = "pagination:mounted";
2773var EVENT_PAGINATION_UPDATED = "pagination:updated";
2774var EVENT_NAVIGATION_MOUNTED = "navigation:mounted";
2775var EVENT_AUTOPLAY_PLAY = "autoplay:play";
2776var EVENT_AUTOPLAY_PLAYING = "autoplay:playing";
2777var EVENT_AUTOPLAY_PAUSE = "autoplay:pause";
2778var EVENT_LAZYLOAD_LOADED = "lazyload:loaded";
2779var EVENT_SLIDE_KEYDOWN = "sk";
2780var EVENT_SHIFTED = "sh";
2781var EVENT_END_INDEX_CHANGED = "ei";
2782
2783function EventInterface(Splide2) {
2784  var bus = Splide2 ? Splide2.event.bus : document.createDocumentFragment();
2785  var binder = EventBinder();
2786
2787  function on(events, callback) {
2788    binder.bind(bus, toArray(events).join(" "), function (e) {
2789      callback.apply(callback, isArray(e.detail) ? e.detail : []);
2790    });
2791  }
2792
2793  function emit(event) {
2794    binder.dispatch(bus, event, slice(arguments, 1));
2795  }
2796
2797  if (Splide2) {
2798    Splide2.event.on(EVENT_DESTROY, binder.destroy);
2799  }
2800
2801  return assign$1(binder, {
2802    bus: bus,
2803    on: on,
2804    off: apply(binder.unbind, bus),
2805    emit: emit
2806  });
2807}
2808
2809function RequestInterval(interval, onInterval, onUpdate, limit) {
2810  var now = Date.now;
2811  var startTime;
2812  var rate = 0;
2813  var id;
2814  var paused = true;
2815  var count = 0;
2816
vendor: 10,121 bytes, lines 2817-3196
2817  function update() {
2818    if (!paused) {
2819      rate = interval ? min((now() - startTime) / interval, 1) : 1;
2820      onUpdate && onUpdate(rate);
2821
2822      if (rate >= 1) {
2823        onInterval();
2824        startTime = now();
2825
2826        if (limit && ++count >= limit) {
2827          return pause();
2828        }
2829      }
2830
2831      id = raf(update);
2832    }
2833  }
2834
2835  function start(resume) {
2836    resume || cancel();
2837    startTime = now() - (resume ? rate * interval : 0);
2838    paused = false;
2839    id = raf(update);
2840  }
2841
2842  function pause() {
2843    paused = true;
2844  }
2845
2846  function rewind() {
2847    startTime = now();
2848    rate = 0;
2849
2850    if (onUpdate) {
2851      onUpdate(rate);
2852    }
2853  }
2854
2855  function cancel() {
2856    id && cancelAnimationFrame(id);
2857    rate = 0;
2858    id = 0;
2859    paused = true;
2860  }
2861
2862  function set(time) {
2863    interval = time;
2864  }
2865
2866  function isPaused() {
2867    return paused;
2868  }
2869
2870  return {
2871    start: start,
2872    rewind: rewind,
2873    pause: pause,
2874    cancel: cancel,
2875    set: set,
2876    isPaused: isPaused
2877  };
2878}
2879
2880function State(initialState) {
2881  var state = initialState;
2882
2883  function set(value) {
2884    state = value;
2885  }
2886
2887  function is(states) {
2888    return includes(toArray(states), state);
2889  }
2890
2891  return {
2892    set: set,
2893    is: is
2894  };
2895}
2896
2897function Throttle(func, duration) {
2898  var interval = RequestInterval(0, func, null, 1);
2899  return function () {
2900    interval.isPaused() && interval.start();
2901  };
2902}
2903
2904function Media(Splide2, Components2, options) {
2905  var state = Splide2.state;
2906  var breakpoints = options.breakpoints || {};
2907  var reducedMotion = options.reducedMotion || {};
2908  var binder = EventBinder();
2909  var queries = [];
2910
2911  function setup() {
2912    var isMin = options.mediaQuery === "min";
2913    ownKeys(breakpoints).sort(function (n, m) {
2914      return isMin ? +n - +m : +m - +n;
2915    }).forEach(function (key) {
2916      register(breakpoints[key], "(" + (isMin ? "min" : "max") + "-width:" + key + "px)");
2917    });
2918    register(reducedMotion, MEDIA_PREFERS_REDUCED_MOTION);
2919    update();
2920  }
2921
2922  function destroy(completely) {
2923    if (completely) {
2924      binder.destroy();
2925    }
2926  }
2927
2928  function register(options2, query) {
2929    var queryList = matchMedia(query);
2930    binder.bind(queryList, "change", update);
2931    queries.push([options2, queryList]);
2932  }
2933
2934  function update() {
2935    var destroyed = state.is(DESTROYED);
2936    var direction = options.direction;
2937    var merged = queries.reduce(function (merged2, entry) {
2938      return merge(merged2, entry[1].matches ? entry[0] : {});
2939    }, {});
2940    omit(options);
2941    set(merged);
2942
2943    if (options.destroy) {
2944      Splide2.destroy(options.destroy === "completely");
2945    } else if (destroyed) {
2946      destroy(true);
2947      Splide2.mount();
2948    } else {
2949      direction !== options.direction && Splide2.refresh();
2950    }
2951  }
2952
2953  function reduce(enable) {
2954    if (matchMedia(MEDIA_PREFERS_REDUCED_MOTION).matches) {
2955      enable ? merge(options, reducedMotion) : omit(options, ownKeys(reducedMotion));
2956    }
2957  }
2958
2959  function set(opts, base, notify) {
2960    merge(options, opts);
2961    base && merge(Object.getPrototypeOf(options), opts);
2962
2963    if (notify || !state.is(CREATED)) {
2964      Splide2.emit(EVENT_UPDATED, options);
2965    }
2966  }
2967
2968  return {
2969    setup: setup,
2970    destroy: destroy,
2971    reduce: reduce,
2972    set: set
2973  };
2974}
2975
2976var ARROW = "Arrow";
2977var ARROW_LEFT = ARROW + "Left";
2978var ARROW_RIGHT = ARROW + "Right";
2979var ARROW_UP = ARROW + "Up";
2980var ARROW_DOWN = ARROW + "Down";
2981var RTL = "rtl";
2982var TTB = "ttb";
2983var ORIENTATION_MAP = {
2984  width: ["height"],
2985  left: ["top", "right"],
2986  right: ["bottom", "left"],
2987  x: ["y"],
2988  X: ["Y"],
2989  Y: ["X"],
2990  ArrowLeft: [ARROW_UP, ARROW_RIGHT],
2991  ArrowRight: [ARROW_DOWN, ARROW_LEFT]
2992};
2993
2994function Direction(Splide2, Components2, options) {
2995  function resolve(prop, axisOnly, direction) {
2996    direction = direction || options.direction;
2997    var index = direction === RTL && !axisOnly ? 1 : direction === TTB ? 0 : -1;
2998    return ORIENTATION_MAP[prop] && ORIENTATION_MAP[prop][index] || prop.replace(/width|left|right/i, function (match, offset) {
2999      var replacement = ORIENTATION_MAP[match.toLowerCase()][index] || match;
3000      return offset > 0 ? replacement.charAt(0).toUpperCase() + replacement.slice(1) : replacement;
3001    });
3002  }
3003
3004  function orient(value) {
3005    return value * (options.direction === RTL ? 1 : -1);
3006  }
3007
3008  return {
3009    resolve: resolve,
3010    orient: orient
3011  };
3012}
3013
3014var ROLE = "role";
3015var TAB_INDEX = "tabindex";
3016var DISABLED = "disabled";
3017var ARIA_PREFIX = "aria-";
3018var ARIA_CONTROLS = ARIA_PREFIX + "controls";
3019var ARIA_CURRENT = ARIA_PREFIX + "current";
3020var ARIA_SELECTED = ARIA_PREFIX + "selected";
3021var ARIA_LABEL = ARIA_PREFIX + "label";
3022var ARIA_LABELLEDBY = ARIA_PREFIX + "labelledby";
3023var ARIA_HIDDEN = ARIA_PREFIX + "hidden";
3024var ARIA_ORIENTATION = ARIA_PREFIX + "orientation";
3025var ARIA_ROLEDESCRIPTION = ARIA_PREFIX + "roledescription";
3026var ARIA_LIVE = ARIA_PREFIX + "live";
3027var ARIA_BUSY = ARIA_PREFIX + "busy";
3028var ARIA_ATOMIC = ARIA_PREFIX + "atomic";
3029var ALL_ATTRIBUTES = [ROLE, TAB_INDEX, DISABLED, ARIA_CONTROLS, ARIA_CURRENT, ARIA_LABEL, ARIA_LABELLEDBY, ARIA_HIDDEN, ARIA_ORIENTATION, ARIA_ROLEDESCRIPTION];
3030var CLASS_PREFIX = PROJECT_CODE + "__";
3031var STATUS_CLASS_PREFIX = "is-";
3032var CLASS_ROOT = PROJECT_CODE;
3033var CLASS_TRACK = CLASS_PREFIX + "track";
3034var CLASS_LIST = CLASS_PREFIX + "list";
3035var CLASS_SLIDE = CLASS_PREFIX + "slide";
3036var CLASS_CLONE = CLASS_SLIDE + "--clone";
3037var CLASS_CONTAINER = CLASS_SLIDE + "__container";
3038var CLASS_ARROWS = CLASS_PREFIX + "arrows";
3039var CLASS_ARROW = CLASS_PREFIX + "arrow";
3040var CLASS_ARROW_PREV = CLASS_ARROW + "--prev";
3041var CLASS_ARROW_NEXT = CLASS_ARROW + "--next";
3042var CLASS_PAGINATION = CLASS_PREFIX + "pagination";
3043var CLASS_PAGINATION_PAGE = CLASS_PAGINATION + "__page";
3044var CLASS_PROGRESS = CLASS_PREFIX + "progress";
3045var CLASS_PROGRESS_BAR = CLASS_PROGRESS + "__bar";
3046var CLASS_TOGGLE = CLASS_PREFIX + "toggle";
3047var CLASS_SPINNER = CLASS_PREFIX + "spinner";
3048var CLASS_SR = CLASS_PREFIX + "sr";
3049var CLASS_INITIALIZED = STATUS_CLASS_PREFIX + "initialized";
3050var CLASS_ACTIVE = STATUS_CLASS_PREFIX + "active";
3051var CLASS_PREV = STATUS_CLASS_PREFIX + "prev";
3052var CLASS_NEXT = STATUS_CLASS_PREFIX + "next";
3053var CLASS_VISIBLE = STATUS_CLASS_PREFIX + "visible";
3054var CLASS_LOADING = STATUS_CLASS_PREFIX + "loading";
3055var CLASS_FOCUS_IN = STATUS_CLASS_PREFIX + "focus-in";
3056var CLASS_OVERFLOW = STATUS_CLASS_PREFIX + "overflow";
3057var STATUS_CLASSES = [CLASS_ACTIVE, CLASS_VISIBLE, CLASS_PREV, CLASS_NEXT, CLASS_LOADING, CLASS_FOCUS_IN, CLASS_OVERFLOW];
3058var CLASSES = {
3059  slide: CLASS_SLIDE,
3060  clone: CLASS_CLONE,
3061  arrows: CLASS_ARROWS,
3062  arrow: CLASS_ARROW,
3063  prev: CLASS_ARROW_PREV,
3064  next: CLASS_ARROW_NEXT,
3065  pagination: CLASS_PAGINATION,
3066  page: CLASS_PAGINATION_PAGE,
3067  spinner: CLASS_SPINNER
3068};
3069
3070function closest(from, selector) {
3071  if (isFunction(from.closest)) {
3072    return from.closest(selector);
3073  }
3074
3075  var elm = from;
3076
3077  while (elm && elm.nodeType === 1) {
3078    if (matches(elm, selector)) {
3079      break;
3080    }
3081
3082    elm = elm.parentElement;
3083  }
3084
3085  return elm;
3086}
3087
3088var FRICTION = 5;
3089var LOG_INTERVAL = 200;
3090var POINTER_DOWN_EVENTS = "touchstart mousedown";
3091var POINTER_MOVE_EVENTS = "touchmove mousemove";
3092var POINTER_UP_EVENTS = "touchend touchcancel mouseup click";
3093
3094function Elements(Splide2, Components2, options) {
3095  var _EventInterface = EventInterface(Splide2),
3096      on = _EventInterface.on,
3097      bind = _EventInterface.bind;
3098
3099  var root = Splide2.root;
3100  var i18n = options.i18n;
3101  var elements = {};
3102  var slides = [];
3103  var rootClasses = [];
3104  var trackClasses = [];
3105  var track;
3106  var list;
3107  var isUsingKey;
3108
3109  function setup() {
3110    collect();
3111    init();
3112    update();
3113  }
3114
3115  function mount() {
3116    on(EVENT_REFRESH, destroy);
3117    on(EVENT_REFRESH, setup);
3118    on(EVENT_UPDATED, update);
3119    bind(document, POINTER_DOWN_EVENTS + " keydown", function (e) {
3120      isUsingKey = e.type === "keydown";
3121    }, {
3122      capture: true
3123    });
3124    bind(root, "focusin", function () {
3125      toggleClass(root, CLASS_FOCUS_IN, !!isUsingKey);
3126    });
3127  }
3128
3129  function destroy(completely) {
3130    var attrs = ALL_ATTRIBUTES.concat("style");
3131    empty(slides);
3132    removeClass(root, rootClasses);
3133    removeClass(track, trackClasses);
3134    removeAttribute([track, list], attrs);
3135    removeAttribute(root, completely ? attrs : ["style", ARIA_ROLEDESCRIPTION]);
3136  }
3137
3138  function update() {
3139    removeClass(root, rootClasses);
3140    removeClass(track, trackClasses);
3141    rootClasses = getClasses(CLASS_ROOT);
3142    trackClasses = getClasses(CLASS_TRACK);
3143    addClass(root, rootClasses);
3144    addClass(track, trackClasses);
3145    setAttribute(root, ARIA_LABEL, options.label);
3146    setAttribute(root, ARIA_LABELLEDBY, options.labelledby);
3147  }
3148
3149  function collect() {
3150    track = find("." + CLASS_TRACK);
3151    list = child(track, "." + CLASS_LIST);
3152    assert(track && list, "A track/list element is missing.");
3153    push(slides, children(list, "." + CLASS_SLIDE + ":not(." + CLASS_CLONE + ")"));
3154    forOwn({
3155      arrows: CLASS_ARROWS,
3156      pagination: CLASS_PAGINATION,
3157      prev: CLASS_ARROW_PREV,
3158      next: CLASS_ARROW_NEXT,
3159      bar: CLASS_PROGRESS_BAR,
3160      toggle: CLASS_TOGGLE
3161    }, function (className, key) {
3162      elements[key] = find("." + className);
3163    });
3164    assign$1(elements, {
3165      root: root,
3166      track: track,
3167      list: list,
3168      slides: slides
3169    });
3170  }
3171
3172  function init() {
3173    var id = root.id || uniqueId(PROJECT_CODE);
3174    var role = options.role;
3175    root.id = id;
3176    track.id = track.id || id + "-track";
3177    list.id = list.id || id + "-list";
3178
3179    if (!getAttribute(root, ROLE) && root.tagName !== "SECTION" && role) {
3180      setAttribute(root, ROLE, role);
3181    }
3182
3183    setAttribute(root, ARIA_ROLEDESCRIPTION, i18n.carousel);
3184    setAttribute(list, ROLE, "presentation");
3185  }
3186
3187  function find(selector) {
3188    var elm = query(root, selector);
3189    return elm && closest(elm, "." + CLASS_ROOT) === root ? elm : void 0;
3190  }
3191
3192  function getClasses(base) {
3193    return [base + "--" + options.type, base + "--" + options.direction, options.drag && base + "--draggable", options.isNavigation && base + "--nav", base === CLASS_ROOT && CLASS_ACTIVE];
3194  }
3195
3196  return assign$1
vendor: 25,309 bytes, lines 3196-4154
3196(elements, {
3197    setup: setup,
3198    mount: mount,
3199    destroy: destroy
3200  });
3201}
3202
3203var SLIDE = "slide";
3204var LOOP = "loop";
3205var FADE = "fade";
3206
3207function Slide$1(Splide2, index, slideIndex, slide) {
3208  var event = EventInterface(Splide2);
3209  var on = event.on,
3210      emit = event.emit,
3211      bind = event.bind;
3212  var Components = Splide2.Components,
3213      root = Splide2.root,
3214      options = Splide2.options;
3215  var isNavigation = options.isNavigation,
3216      updateOnMove = options.updateOnMove,
3217      i18n = options.i18n,
3218      pagination = options.pagination,
3219      slideFocus = options.slideFocus;
3220  var resolve = Components.Direction.resolve;
3221  var styles = getAttribute(slide, "style");
3222  var label = getAttribute(slide, ARIA_LABEL);
3223  var isClone = slideIndex > -1;
3224  var container = child(slide, "." + CLASS_CONTAINER);
3225  var destroyed;
3226
3227  function mount() {
3228    if (!isClone) {
3229      slide.id = root.id + "-slide" + pad(index + 1);
3230      setAttribute(slide, ROLE, pagination ? "tabpanel" : "group");
3231      setAttribute(slide, ARIA_ROLEDESCRIPTION, i18n.slide);
3232      setAttribute(slide, ARIA_LABEL, label || format(i18n.slideLabel, [index + 1, Splide2.length]));
3233    }
3234
3235    listen();
3236  }
3237
3238  function listen() {
3239    bind(slide, "click", apply(emit, EVENT_CLICK, self));
3240    bind(slide, "keydown", apply(emit, EVENT_SLIDE_KEYDOWN, self));
3241    on([EVENT_MOVED, EVENT_SHIFTED, EVENT_SCROLLED], update);
3242    on(EVENT_NAVIGATION_MOUNTED, initNavigation);
3243
3244    if (updateOnMove) {
3245      on(EVENT_MOVE, onMove);
3246    }
3247  }
3248
3249  function destroy() {
3250    destroyed = true;
3251    event.destroy();
3252    removeClass(slide, STATUS_CLASSES);
3253    removeAttribute(slide, ALL_ATTRIBUTES);
3254    setAttribute(slide, "style", styles);
3255    setAttribute(slide, ARIA_LABEL, label || "");
3256  }
3257
3258  function initNavigation() {
3259    var controls = Splide2.splides.map(function (target) {
3260      var Slide2 = target.splide.Components.Slides.getAt(index);
3261      return Slide2 ? Slide2.slide.id : "";
3262    }).join(" ");
3263    setAttribute(slide, ARIA_LABEL, format(i18n.slideX, (isClone ? slideIndex : index) + 1));
3264    setAttribute(slide, ARIA_CONTROLS, controls);
3265    setAttribute(slide, ROLE, slideFocus ? "button" : "");
3266    slideFocus && removeAttribute(slide, ARIA_ROLEDESCRIPTION);
3267  }
3268
3269  function onMove() {
3270    if (!destroyed) {
3271      update();
3272    }
3273  }
3274
3275  function update() {
3276    if (!destroyed) {
3277      var curr = Splide2.index;
3278      updateActivity();
3279      updateVisibility();
3280      toggleClass(slide, CLASS_PREV, index === curr - 1);
3281      toggleClass(slide, CLASS_NEXT, index === curr + 1);
3282    }
3283  }
3284
3285  function updateActivity() {
3286    var active = isActive();
3287
3288    if (active !== hasClass(slide, CLASS_ACTIVE)) {
3289      toggleClass(slide, CLASS_ACTIVE, active);
3290      setAttribute(slide, ARIA_CURRENT, isNavigation && active || "");
3291      emit(active ? EVENT_ACTIVE : EVENT_INACTIVE, self);
3292    }
3293  }
3294
3295  function updateVisibility() {
3296    var visible = isVisible();
3297    var hidden = !visible && (!isActive() || isClone);
3298
3299    if (!Splide2.state.is([MOVING, SCROLLING])) {
3300      setAttribute(slide, ARIA_HIDDEN, hidden || "");
3301    }
3302
3303    setAttribute(queryAll(slide, options.focusableNodes || ""), TAB_INDEX, hidden ? -1 : "");
3304
3305    if (slideFocus) {
3306      setAttribute(slide, TAB_INDEX, hidden ? -1 : 0);
3307    }
3308
3309    if (visible !== hasClass(slide, CLASS_VISIBLE)) {
3310      toggleClass(slide, CLASS_VISIBLE, visible);
3311      emit(visible ? EVENT_VISIBLE : EVENT_HIDDEN, self);
3312    }
3313
3314    if (!visible && document.activeElement === slide) {
3315      var Slide2 = Components.Slides.getAt(Splide2.index);
3316      Slide2 && focus(Slide2.slide);
3317    }
3318  }
3319
3320  function style$1(prop, value, useContainer) {
3321    style(useContainer && container || slide, prop, value);
3322  }
3323
3324  function isActive() {
3325    var curr = Splide2.index;
3326    return curr === index || options.cloneStatus && curr === slideIndex;
3327  }
3328
3329  function isVisible() {
3330    if (Splide2.is(FADE)) {
3331      return isActive();
3332    }
3333
3334    var trackRect = rect(Components.Elements.track);
3335    var slideRect = rect(slide);
3336    var left = resolve("left", true);
3337    var right = resolve("right", true);
3338    return floor(trackRect[left]) <= ceil(slideRect[left]) && floor(slideRect[right]) <= ceil(trackRect[right]);
3339  }
3340
3341  function isWithin(from, distance) {
3342    var diff = abs(from - index);
3343
3344    if (!isClone && (options.rewind || Splide2.is(LOOP))) {
3345      diff = min(diff, Splide2.length - diff);
3346    }
3347
3348    return diff <= distance;
3349  }
3350
3351  var self = {
3352    index: index,
3353    slideIndex: slideIndex,
3354    slide: slide,
3355    container: container,
3356    isClone: isClone,
3357    mount: mount,
3358    destroy: destroy,
3359    update: update,
3360    style: style$1,
3361    isWithin: isWithin
3362  };
3363  return self;
3364}
3365
3366function Slides(Splide2, Components2, options) {
3367  var _EventInterface2 = EventInterface(Splide2),
3368      on = _EventInterface2.on,
3369      emit = _EventInterface2.emit,
3370      bind = _EventInterface2.bind;
3371
3372  var _Components2$Elements = Components2.Elements,
3373      slides = _Components2$Elements.slides,
3374      list = _Components2$Elements.list;
3375  var Slides2 = [];
3376
3377  function mount() {
3378    init();
3379    on(EVENT_REFRESH, destroy);
3380    on(EVENT_REFRESH, init);
3381  }
3382
3383  function init() {
3384    slides.forEach(function (slide, index) {
3385      register(slide, index, -1);
3386    });
3387  }
3388
3389  function destroy() {
3390    forEach$1(function (Slide2) {
3391      Slide2.destroy();
3392    });
3393    empty(Slides2);
3394  }
3395
3396  function update() {
3397    forEach$1(function (Slide2) {
3398      Slide2.update();
3399    });
3400  }
3401
3402  function register(slide, index, slideIndex) {
3403    var object = Slide$1(Splide2, index, slideIndex, slide);
3404    object.mount();
3405    Slides2.push(object);
3406    Slides2.sort(function (Slide1, Slide2) {
3407      return Slide1.index - Slide2.index;
3408    });
3409  }
3410
3411  function get(excludeClones) {
3412    return excludeClones ? filter(function (Slide2) {
3413      return !Slide2.isClone;
3414    }) : Slides2;
3415  }
3416
3417  function getIn(page) {
3418    var Controller = Components2.Controller;
3419    var index = Controller.toIndex(page);
3420    var max = Controller.hasFocus() ? 1 : options.perPage;
3421    return filter(function (Slide2) {
3422      return between(Slide2.index, index, index + max - 1);
3423    });
3424  }
3425
3426  function getAt(index) {
3427    return filter(index)[0];
3428  }
3429
3430  function add(items, index) {
3431    forEach(items, function (slide) {
3432      if (isString(slide)) {
3433        slide = parseHtml(slide);
3434      }
3435
3436      if (isHTMLElement(slide)) {
3437        var ref = slides[index];
3438        ref ? before(slide, ref) : append(list, slide);
3439        addClass(slide, options.classes.slide);
3440        observeImages(slide, apply(emit, EVENT_RESIZE));
3441      }
3442    });
3443    emit(EVENT_REFRESH);
3444  }
3445
3446  function remove$1(matcher) {
3447    remove(filter(matcher).map(function (Slide2) {
3448      return Slide2.slide;
3449    }));
3450    emit(EVENT_REFRESH);
3451  }
3452
3453  function forEach$1(iteratee, excludeClones) {
3454    get(excludeClones).forEach(iteratee);
3455  }
3456
3457  function filter(matcher) {
3458    return Slides2.filter(isFunction(matcher) ? matcher : function (Slide2) {
3459      return isString(matcher) ? matches(Slide2.slide, matcher) : includes(toArray(matcher), Slide2.index);
3460    });
3461  }
3462
3463  function style(prop, value, useContainer) {
3464    forEach$1(function (Slide2) {
3465      Slide2.style(prop, value, useContainer);
3466    });
3467  }
3468
3469  function observeImages(elm, callback) {
3470    var images = queryAll(elm, "img");
3471    var length = images.length;
3472
3473    if (length) {
3474      images.forEach(function (img) {
3475        bind(img, "load error", function () {
3476          if (! --length) {
3477            callback();
3478          }
3479        });
3480      });
3481    } else {
3482      callback();
3483    }
3484  }
3485
3486  function getLength(excludeClones) {
3487    return excludeClones ? slides.length : Slides2.length;
3488  }
3489
3490  function isEnough() {
3491    return Slides2.length > options.perPage;
3492  }
3493
3494  return {
3495    mount: mount,
3496    destroy: destroy,
3497    update: update,
3498    register: register,
3499    get: get,
3500    getIn: getIn,
3501    getAt: getAt,
3502    add: add,
3503    remove: remove$1,
3504    forEach: forEach$1,
3505    filter: filter,
3506    style: style,
3507    getLength: getLength,
3508    isEnough: isEnough
3509  };
3510}
3511
3512function Layout(Splide2, Components2, options) {
3513  var _EventInterface3 = EventInterface(Splide2),
3514      on = _EventInterface3.on,
3515      bind = _EventInterface3.bind,
3516      emit = _EventInterface3.emit;
3517
3518  var Slides = Components2.Slides;
3519  var resolve = Components2.Direction.resolve;
3520  var _Components2$Elements2 = Components2.Elements,
3521      root = _Components2$Elements2.root,
3522      track = _Components2$Elements2.track,
3523      list = _Components2$Elements2.list;
3524  var getAt = Slides.getAt,
3525      styleSlides = Slides.style;
3526  var vertical;
3527  var rootRect;
3528  var overflow;
3529
3530  function mount() {
3531    init();
3532    bind(window, "resize load", Throttle(apply(emit, EVENT_RESIZE)));
3533    on([EVENT_UPDATED, EVENT_REFRESH], init);
3534    on(EVENT_RESIZE, resize);
3535  }
3536
3537  function init() {
3538    vertical = options.direction === TTB;
3539    style(root, "maxWidth", unit(options.width));
3540    style(track, resolve("paddingLeft"), cssPadding(false));
3541    style(track, resolve("paddingRight"), cssPadding(true));
3542    resize(true);
3543  }
3544
3545  function resize(force) {
3546    var newRect = rect(root);
3547
3548    if (force || rootRect.width !== newRect.width || rootRect.height !== newRect.height) {
3549      style(track, "height", cssTrackHeight());
3550      styleSlides(resolve("marginRight"), unit(options.gap));
3551      styleSlides("width", cssSlideWidth());
3552      styleSlides("height", cssSlideHeight(), true);
3553      rootRect = newRect;
3554      emit(EVENT_RESIZED);
3555
3556      if (overflow !== (overflow = isOverflow())) {
3557        toggleClass(root, CLASS_OVERFLOW, overflow);
3558        emit(EVENT_OVERFLOW, overflow);
3559      }
3560    }
3561  }
3562
3563  function cssPadding(right) {
3564    var padding = options.padding;
3565    var prop = resolve(right ? "right" : "left");
3566    return padding && unit(padding[prop] || (isObject(padding) ? 0 : padding)) || "0px";
3567  }
3568
3569  function cssTrackHeight() {
3570    var height = "";
3571
3572    if (vertical) {
3573      height = cssHeight();
3574      assert(height, "height or heightRatio is missing.");
3575      height = "calc(" + height + " - " + cssPadding(false) + " - " + cssPadding(true) + ")";
3576    }
3577
3578    return height;
3579  }
3580
3581  function cssHeight() {
3582    return unit(options.height || rect(list).width * options.heightRatio);
3583  }
3584
3585  function cssSlideWidth() {
3586    return options.autoWidth ? null : unit(options.fixedWidth) || (vertical ? "" : cssSlideSize());
3587  }
3588
3589  function cssSlideHeight() {
3590    return unit(options.fixedHeight) || (vertical ? options.autoHeight ? null : cssSlideSize() : cssHeight());
3591  }
3592
3593  function cssSlideSize() {
3594    var gap = unit(options.gap);
3595    return "calc((100%" + (gap && " + " + gap) + ")/" + (options.perPage || 1) + (gap && " - " + gap) + ")";
3596  }
3597
3598  function listSize() {
3599    return rect(list)[resolve("width")];
3600  }
3601
3602  function slideSize(index, withoutGap) {
3603    var Slide = getAt(index || 0);
3604    return Slide ? rect(Slide.slide)[resolve("width")] + (withoutGap ? 0 : getGap()) : 0;
3605  }
3606
3607  function totalSize(index, withoutGap) {
3608    var Slide = getAt(index);
3609
3610    if (Slide) {
3611      var right = rect(Slide.slide)[resolve("right")];
3612      var left = rect(list)[resolve("left")];
3613      return abs(right - left) + (withoutGap ? 0 : getGap());
3614    }
3615
3616    return 0;
3617  }
3618
3619  function sliderSize(withoutGap) {
3620    return totalSize(Splide2.length - 1) - totalSize(0) + slideSize(0, withoutGap);
3621  }
3622
3623  function getGap() {
3624    var Slide = getAt(0);
3625    return Slide && parseFloat(style(Slide.slide, resolve("marginRight"))) || 0;
3626  }
3627
3628  function getPadding(right) {
3629    return parseFloat(style(track, resolve("padding" + (right ? "Right" : "Left")))) || 0;
3630  }
3631
3632  function isOverflow() {
3633    return Splide2.is(FADE) || sliderSize(true) > listSize();
3634  }
3635
3636  return {
3637    mount: mount,
3638    resize: resize,
3639    listSize: listSize,
3640    slideSize: slideSize,
3641    sliderSize: sliderSize,
3642    totalSize: totalSize,
3643    getPadding: getPadding,
3644    isOverflow: isOverflow
3645  };
3646}
3647
3648var MULTIPLIER = 2;
3649
3650function Clones(Splide2, Components2, options) {
3651  var event = EventInterface(Splide2);
3652  var on = event.on;
3653  var Elements = Components2.Elements,
3654      Slides = Components2.Slides;
3655  var resolve = Components2.Direction.resolve;
3656  var clones = [];
3657  var cloneCount;
3658
3659  function mount() {
3660    on(EVENT_REFRESH, remount);
3661    on([EVENT_UPDATED, EVENT_RESIZE], observe);
3662
3663    if (cloneCount = computeCloneCount()) {
3664      generate(cloneCount);
3665      Components2.Layout.resize(true);
3666    }
3667  }
3668
3669  function remount() {
3670    destroy();
3671    mount();
3672  }
3673
3674  function destroy() {
3675    remove(clones);
3676    empty(clones);
3677    event.destroy();
3678  }
3679
3680  function observe() {
3681    var count = computeCloneCount();
3682
3683    if (cloneCount !== count) {
3684      if (cloneCount < count || !count) {
3685        event.emit(EVENT_REFRESH);
3686      }
3687    }
3688  }
3689
3690  function generate(count) {
3691    var slides = Slides.get().slice();
3692    var length = slides.length;
3693
3694    if (length) {
3695      while (slides.length < count) {
3696        push(slides, slides);
3697      }
3698
3699      push(slides.slice(-count), slides.slice(0, count)).forEach(function (Slide, index) {
3700        var isHead = index < count;
3701        var clone = cloneDeep(Slide.slide, index);
3702        isHead ? before(clone, slides[0].slide) : append(Elements.list, clone);
3703        push(clones, clone);
3704        Slides.register(clone, index - count + (isHead ? 0 : length), Slide.index);
3705      });
3706    }
3707  }
3708
3709  function cloneDeep(elm, index) {
3710    var clone = elm.cloneNode(true);
3711    addClass(clone, options.classes.clone);
3712    clone.id = Splide2.root.id + "-clone" + pad(index + 1);
3713    return clone;
3714  }
3715
3716  function computeCloneCount() {
3717    var clones2 = options.clones;
3718
3719    if (!Splide2.is(LOOP)) {
3720      clones2 = 0;
3721    } else if (isUndefined(clones2)) {
3722      var fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
3723      var fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
3724      clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage * MULTIPLIER;
3725    }
3726
3727    return clones2;
3728  }
3729
3730  return {
3731    mount: mount,
3732    destroy: destroy
3733  };
3734}
3735
3736function Move(Splide2, Components2, options) {
3737  var _EventInterface4 = EventInterface(Splide2),
3738      on = _EventInterface4.on,
3739      emit = _EventInterface4.emit;
3740
3741  var set = Splide2.state.set;
3742  var _Components2$Layout = Components2.Layout,
3743      slideSize = _Components2$Layout.slideSize,
3744      getPadding = _Components2$Layout.getPadding,
3745      totalSize = _Components2$Layout.totalSize,
3746      listSize = _Components2$Layout.listSize,
3747      sliderSize = _Components2$Layout.sliderSize;
3748  var _Components2$Directio = Components2.Direction,
3749      resolve = _Components2$Directio.resolve,
3750      orient = _Components2$Directio.orient;
3751  var _Components2$Elements3 = Components2.Elements,
3752      list = _Components2$Elements3.list,
3753      track = _Components2$Elements3.track;
3754  var Transition;
3755
3756  function mount() {
3757    Transition = Components2.Transition;
3758    on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
3759  }
3760
3761  function reposition() {
3762    if (!Components2.Controller.isBusy()) {
3763      Components2.Scroll.cancel();
3764      jump(Splide2.index);
3765      Components2.Slides.update();
3766    }
3767  }
3768
3769  function move(dest, index, prev, callback) {
3770    if (dest !== index && canShift(dest > prev)) {
3771      cancel();
3772      translate(shift(getPosition(), dest > prev), true);
3773    }
3774
3775    set(MOVING);
3776    emit(EVENT_MOVE, index, prev, dest);
3777    Transition.start(index, function () {
3778      set(IDLE);
3779      emit(EVENT_MOVED, index, prev, dest);
3780      callback && callback();
3781    });
3782  }
3783
3784  function jump(index) {
3785    translate(toPosition(index, true));
3786  }
3787
3788  function translate(position, preventLoop) {
3789    if (!Splide2.is(FADE)) {
3790      var destination = preventLoop ? position : loop(position);
3791      style(list, "transform", "translate" + resolve("X") + "(" + destination + "px)");
3792      position !== destination && emit(EVENT_SHIFTED);
3793    }
3794  }
3795
3796  function loop(position) {
3797    if (Splide2.is(LOOP)) {
3798      var index = toIndex(position);
3799      var exceededMax = index > Components2.Controller.getEnd();
3800      var exceededMin = index < 0;
3801
3802      if (exceededMin || exceededMax) {
3803        position = shift(position, exceededMax);
3804      }
3805    }
3806
3807    return position;
3808  }
3809
3810  function shift(position, backwards) {
3811    var excess = position - getLimit(backwards);
3812    var size = sliderSize();
3813    position -= orient(size * (ceil(abs(excess) / size) || 1)) * (backwards ? 1 : -1);
3814    return position;
3815  }
3816
3817  function cancel() {
3818    translate(getPosition(), true);
3819    Transition.cancel();
3820  }
3821
3822  function toIndex(position) {
3823    var Slides = Components2.Slides.get();
3824    var index = 0;
3825    var minDistance = Infinity;
3826
3827    for (var i = 0; i < Slides.length; i++) {
3828      var slideIndex = Slides[i].index;
3829      var distance = abs(toPosition(slideIndex, true) - position);
3830
3831      if (distance <= minDistance) {
3832        minDistance = distance;
3833        index = slideIndex;
3834      } else {
3835        break;
3836      }
3837    }
3838
3839    return index;
3840  }
3841
3842  function toPosition(index, trimming) {
3843    var position = orient(totalSize(index - 1) - offset(index));
3844    return trimming ? trim(position) : position;
3845  }
3846
3847  function getPosition() {
3848    var left = resolve("left");
3849    return rect(list)[left] - rect(track)[left] + orient(getPadding(false));
3850  }
3851
3852  function trim(position) {
3853    if (options.trimSpace && Splide2.is(SLIDE)) {
3854      position = clamp(position, 0, orient(sliderSize(true) - listSize()));
3855    }
3856
3857    return position;
3858  }
3859
3860  function offset(index) {
3861    var focus = options.focus;
3862    return focus === "center" ? (listSize() - slideSize(index, true)) / 2 : +focus * slideSize(index) || 0;
3863  }
3864
3865  function getLimit(max) {
3866    return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
3867  }
3868
3869  function canShift(backwards) {
3870    var shifted = orient(shift(getPosition(), backwards));
3871    return backwards ? shifted >= 0 : shifted <= list[resolve("scrollWidth")] - rect(track)[resolve("width")];
3872  }
3873
3874  function exceededLimit(max, position) {
3875    position = isUndefined(position) ? getPosition() : position;
3876    var exceededMin = max !== true && orient(position) < orient(getLimit(false));
3877    var exceededMax = max !== false && orient(position) > orient(getLimit(true));
3878    return exceededMin || exceededMax;
3879  }
3880
3881  return {
3882    mount: mount,
3883    move: move,
3884    jump: jump,
3885    translate: translate,
3886    shift: shift,
3887    cancel: cancel,
3888    toIndex: toIndex,
3889    toPosition: toPosition,
3890    getPosition: getPosition,
3891    getLimit: getLimit,
3892    exceededLimit: exceededLimit,
3893    reposition: reposition
3894  };
3895}
3896
3897function Controller(Splide2, Components2, options) {
3898  var _EventInterface5 = EventInterface(Splide2),
3899      on = _EventInterface5.on,
3900      emit = _EventInterface5.emit;
3901
3902  var Move = Components2.Move;
3903  var getPosition = Move.getPosition,
3904      getLimit = Move.getLimit,
3905      toPosition = Move.toPosition;
3906  var _Components2$Slides = Components2.Slides,
3907      isEnough = _Components2$Slides.isEnough,
3908      getLength = _Components2$Slides.getLength;
3909  var omitEnd = options.omitEnd;
3910  var isLoop = Splide2.is(LOOP);
3911  var isSlide = Splide2.is(SLIDE);
3912  var getNext = apply(getAdjacent, false);
3913  var getPrev = apply(getAdjacent, true);
3914  var currIndex = options.start || 0;
3915  var endIndex;
3916  var prevIndex = currIndex;
3917  var slideCount;
3918  var perMove;
3919  var perPage;
3920
3921  function mount() {
3922    init();
3923    on([EVENT_UPDATED, EVENT_REFRESH, EVENT_END_INDEX_CHANGED], init);
3924    on(EVENT_RESIZED, onResized);
3925  }
3926
3927  function init() {
3928    slideCount = getLength(true);
3929    perMove = options.perMove;
3930    perPage = options.perPage;
3931    endIndex = getEnd();
3932    var index = clamp(currIndex, 0, omitEnd ? endIndex : slideCount - 1);
3933
3934    if (index !== currIndex) {
3935      currIndex = index;
3936      Move.reposition();
3937    }
3938  }
3939
3940  function onResized() {
3941    if (endIndex !== getEnd()) {
3942      emit(EVENT_END_INDEX_CHANGED);
3943    }
3944  }
3945
3946  function go(control, allowSameIndex, callback) {
3947    if (!isBusy()) {
3948      var dest = parse(control);
3949      var index = loop(dest);
3950
3951      if (index > -1 && (allowSameIndex || index !== currIndex)) {
3952        setIndex(index);
3953        Move.move(dest, index, prevIndex, callback);
3954      }
3955    }
3956  }
3957
3958  function scroll(destination, duration, snap, callback) {
3959    Components2.Scroll.scroll(destination, duration, snap, function () {
3960      var index = loop(Move.toIndex(getPosition()));
3961      setIndex(omitEnd ? min(index, endIndex) : index);
3962      callback && callback();
3963    });
3964  }
3965
3966  function parse(control) {
3967    var index = currIndex;
3968
3969    if (isString(control)) {
3970      var _ref = control.match(/([+\-<>])(\d+)?/) || [],
3971          indicator = _ref[1],
3972          number = _ref[2];
3973
3974      if (indicator === "+" || indicator === "-") {
3975        index = computeDestIndex(currIndex + +("" + indicator + (+number || 1)), currIndex);
3976      } else if (indicator === ">") {
3977        index = number ? toIndex(+number) : getNext(true);
3978      } else if (indicator === "<") {
3979        index = getPrev(true);
3980      }
3981    } else {
3982      index = isLoop ? control : clamp(control, 0, endIndex);
3983    }
3984
3985    return index;
3986  }
3987
3988  function getAdjacent(prev, destination) {
3989    var number = perMove || (hasFocus() ? 1 : perPage);
3990    var dest = computeDestIndex(currIndex + number * (prev ? -1 : 1), currIndex, !(perMove || hasFocus()));
3991
3992    if (dest === -1 && isSlide) {
3993      if (!approximatelyEqual(getPosition(), getLimit(!prev), 1)) {
3994        return prev ? 0 : endIndex;
3995      }
3996    }
3997
3998    return destination ? dest : loop(dest);
3999  }
4000
4001  function computeDestIndex(dest, from, snapPage) {
4002    if (isEnough() || hasFocus()) {
4003      var index = computeMovableDestIndex(dest);
4004
4005      if (index !== dest) {
4006        from = dest;
4007        dest = index;
4008        snapPage = false;
4009      }
4010
4011      if (dest < 0 || dest > endIndex) {
4012        if (!perMove && (between(0, dest, from, true) || between(endIndex, from, dest, true))) {
4013          dest = toIndex(toPage(dest));
4014        } else {
4015          if (isLoop) {
4016            dest = snapPage ? dest < 0 ? -(slideCount % perPage || perPage) : slideCount : dest;
4017          } else if (options.rewind) {
4018            dest = dest < 0 ? endIndex : 0;
4019          } else {
4020            dest = -1;
4021          }
4022        }
4023      } else {
4024        if (snapPage && dest !== from) {
4025          dest = toIndex(toPage(from) + (dest < from ? -1 : 1));
4026        }
4027      }
4028    } else {
4029      dest = -1;
4030    }
4031
4032    return dest;
4033  }
4034
4035  function computeMovableDestIndex(dest) {
4036    if (isSlide && options.trimSpace === "move" && dest !== currIndex) {
4037      var position = getPosition();
4038
4039      while (position === toPosition(dest, true) && between(dest, 0, Splide2.length - 1, !options.rewind)) {
4040        dest < currIndex ? --dest : ++dest;
4041      }
4042    }
4043
4044    return dest;
4045  }
4046
4047  function loop(index) {
4048    return isLoop ? (index + slideCount) % slideCount || 0 : index;
4049  }
4050
4051  function getEnd() {
4052    var end = slideCount - (hasFocus() || isLoop && perMove ? 1 : perPage);
4053
4054    while (omitEnd && end-- > 0) {
4055      if (toPosition(slideCount - 1, true) !== toPosition(end, true)) {
4056        end++;
4057        break;
4058      }
4059    }
4060
4061    return clamp(end, 0, slideCount - 1);
4062  }
4063
4064  function toIndex(page) {
4065    return clamp(hasFocus() ? page : perPage * page, 0, endIndex);
4066  }
4067
4068  function toPage(index) {
4069    return hasFocus() ? min(index, endIndex) : floor((index >= endIndex ? slideCount - 1 : index) / perPage);
4070  }
4071
4072  function toDest(destination) {
4073    var closest = Move.toIndex(destination);
4074    return isSlide ? clamp(closest, 0, endIndex) : closest;
4075  }
4076
4077  function setIndex(index) {
4078    if (index !== currIndex) {
4079      prevIndex = currIndex;
4080      currIndex = index;
4081    }
4082  }
4083
4084  function getIndex(prev) {
4085    return prev ? prevIndex : currIndex;
4086  }
4087
4088  function hasFocus() {
4089    return !isUndefined(options.focus) || options.isNavigation;
4090  }
4091
4092  function isBusy() {
4093    return Splide2.state.is([MOVING, SCROLLING]) && !!options.waitForTransition;
4094  }
4095
4096  return {
4097    mount: mount,
4098    go: go,
4099    scroll: scroll,
4100    getNext: getNext,
4101    getPrev: getPrev,
4102    getAdjacent: getAdjacent,
4103    getEnd: getEnd,
4104    setIndex: setIndex,
4105    getIndex: getIndex,
4106    toIndex: toIndex,
4107    toPage: toPage,
4108    toDest: toDest,
4109    hasFocus: hasFocus,
4110    isBusy: isBusy
4111  };
4112}
4113
4114var XML_NAME_SPACE = "http://www.w3.org/2000/svg";
4115var PATH = "m15.5 0.932-4.3 4.38 14.5 14.6-14.5 14.5 4.3 4.4 14.6-14.6 4.4-4.3-4.4-4.4-14.6-14.6z";
4116var SIZE = 40;
4117
4118function Arrows(Splide2, Components2, options) {
4119  var event = EventInterface(Splide2);
4120  var on = event.on,
4121      bind = event.bind,
4122      emit = event.emit;
4123  var classes = options.classes,
4124      i18n = options.i18n;
4125  var Elements = Components2.Elements,
4126      Controller = Components2.Controller;
4127  var placeholder = Elements.arrows,
4128      track = Elements.track;
4129  var wrapper = placeholder;
4130  var prev = Elements.prev;
4131  var next = Elements.next;
4132  var created;
4133  var wrapperClasses;
4134  var arrows = {};
4135
4136  function mount() {
4137    init();
4138    on(EVENT_UPDATED, remount);
4139  }
4140
4141  function remount() {
4142    destroy();
4143    mount();
4144  }
4145
4146  function init() {
4147    var enabled = options.arrows;
4148
4149    if (enabled && !(prev && next)) {
4150      createArrows();
4151    }
4152
4153    if (prev && next) {
4154      assign
4154$1
vendor: 30,354 bytes, lines 4154-5367
4154(arrows, {
4155        prev: prev,
4156        next: next
4157      });
4158      display(wrapper, enabled ? "" : "none");
4159      addClass(wrapper, wrapperClasses = CLASS_ARROWS + "--" + options.direction);
4160
4161      if (enabled) {
4162        listen();
4163        update();
4164        setAttribute([prev, next], ARIA_CONTROLS, track.id);
4165        emit(EVENT_ARROWS_MOUNTED, prev, next);
4166      }
4167    }
4168  }
4169
4170  function destroy() {
4171    event.destroy();
4172    removeClass(wrapper, wrapperClasses);
4173
4174    if (created) {
4175      remove(placeholder ? [prev, next] : wrapper);
4176      prev = next = null;
4177    } else {
4178      removeAttribute([prev, next], ALL_ATTRIBUTES);
4179    }
4180  }
4181
4182  function listen() {
4183    on([EVENT_MOUNTED, EVENT_MOVED, EVENT_REFRESH, EVENT_SCROLLED, EVENT_END_INDEX_CHANGED], update);
4184    bind(next, "click", apply(go, ">"));
4185    bind(prev, "click", apply(go, "<"));
4186  }
4187
4188  function go(control) {
4189    Controller.go(control, true);
4190  }
4191
4192  function createArrows() {
4193    wrapper = placeholder || create("div", classes.arrows);
4194    prev = createArrow(true);
4195    next = createArrow(false);
4196    created = true;
4197    append(wrapper, [prev, next]);
4198    !placeholder && before(wrapper, track);
4199  }
4200
4201  function createArrow(prev2) {
4202    var arrow = "<button class=\"" + classes.arrow + " " + (prev2 ? classes.prev : classes.next) + "\" type=\"button\"><svg xmlns=\"" + XML_NAME_SPACE + "\" viewBox=\"0 0 " + SIZE + " " + SIZE + "\" width=\"" + SIZE + "\" height=\"" + SIZE + "\" focusable=\"false\"><path d=\"" + (options.arrowPath || PATH) + "\" />";
4203    return parseHtml(arrow);
4204  }
4205
4206  function update() {
4207    if (prev && next) {
4208      var index = Splide2.index;
4209      var prevIndex = Controller.getPrev();
4210      var nextIndex = Controller.getNext();
4211      var prevLabel = prevIndex > -1 && index < prevIndex ? i18n.last : i18n.prev;
4212      var nextLabel = nextIndex > -1 && index > nextIndex ? i18n.first : i18n.next;
4213      prev.disabled = prevIndex < 0;
4214      next.disabled = nextIndex < 0;
4215      setAttribute(prev, ARIA_LABEL, prevLabel);
4216      setAttribute(next, ARIA_LABEL, nextLabel);
4217      emit(EVENT_ARROWS_UPDATED, prev, next, prevIndex, nextIndex);
4218    }
4219  }
4220
4221  return {
4222    arrows: arrows,
4223    mount: mount,
4224    destroy: destroy,
4225    update: update
4226  };
4227}
4228
4229var INTERVAL_DATA_ATTRIBUTE = DATA_ATTRIBUTE + "-interval";
4230
4231function Autoplay(Splide2, Components2, options) {
4232  var _EventInterface6 = EventInterface(Splide2),
4233      on = _EventInterface6.on,
4234      bind = _EventInterface6.bind,
4235      emit = _EventInterface6.emit;
4236
4237  var interval = RequestInterval(options.interval, Splide2.go.bind(Splide2, ">"), onAnimationFrame);
4238  var isPaused = interval.isPaused;
4239  var Elements = Components2.Elements,
4240      _Components2$Elements4 = Components2.Elements,
4241      root = _Components2$Elements4.root,
4242      toggle = _Components2$Elements4.toggle;
4243  var autoplay = options.autoplay;
4244  var hovered;
4245  var focused;
4246  var stopped = autoplay === "pause";
4247
4248  function mount() {
4249    if (autoplay) {
4250      listen();
4251      toggle && setAttribute(toggle, ARIA_CONTROLS, Elements.track.id);
4252      stopped || play();
4253      update();
4254    }
4255  }
4256
4257  function listen() {
4258    if (options.pauseOnHover) {
4259      bind(root, "mouseenter mouseleave", function (e) {
4260        hovered = e.type === "mouseenter";
4261        autoToggle();
4262      });
4263    }
4264
4265    if (options.pauseOnFocus) {
4266      bind(root, "focusin focusout", function (e) {
4267        focused = e.type === "focusin";
4268        autoToggle();
4269      });
4270    }
4271
4272    if (toggle) {
4273      bind(toggle, "click", function () {
4274        stopped ? play() : pause(true);
4275      });
4276    }
4277
4278    on([EVENT_MOVE, EVENT_SCROLL, EVENT_REFRESH], interval.rewind);
4279    on(EVENT_MOVE, onMove);
4280  }
4281
4282  function play() {
4283    if (isPaused() && Components2.Slides.isEnough()) {
4284      interval.start(!options.resetProgress);
4285      focused = hovered = stopped = false;
4286      update();
4287      emit(EVENT_AUTOPLAY_PLAY);
4288    }
4289  }
4290
4291  function pause(stop) {
4292    if (stop === void 0) {
4293      stop = true;
4294    }
4295
4296    stopped = !!stop;
4297    update();
4298
4299    if (!isPaused()) {
4300      interval.pause();
4301      emit(EVENT_AUTOPLAY_PAUSE);
4302    }
4303  }
4304
4305  function autoToggle() {
4306    if (!stopped) {
4307      hovered || focused ? pause(false) : play();
4308    }
4309  }
4310
4311  function update() {
4312    if (toggle) {
4313      toggleClass(toggle, CLASS_ACTIVE, !stopped);
4314      setAttribute(toggle, ARIA_LABEL, options.i18n[stopped ? "play" : "pause"]);
4315    }
4316  }
4317
4318  function onAnimationFrame(rate) {
4319    var bar = Elements.bar;
4320    bar && style(bar, "width", rate * 100 + "%");
4321    emit(EVENT_AUTOPLAY_PLAYING, rate);
4322  }
4323
4324  function onMove(index) {
4325    var Slide = Components2.Slides.getAt(index);
4326    interval.set(Slide && +getAttribute(Slide.slide, INTERVAL_DATA_ATTRIBUTE) || options.interval);
4327  }
4328
4329  return {
4330    mount: mount,
4331    destroy: interval.cancel,
4332    play: play,
4333    pause: pause,
4334    isPaused: isPaused
4335  };
4336}
4337
4338function Cover(Splide2, Components2, options) {
4339  var _EventInterface7 = EventInterface(Splide2),
4340      on = _EventInterface7.on;
4341
4342  function mount() {
4343    if (options.cover) {
4344      on(EVENT_LAZYLOAD_LOADED, apply(toggle, true));
4345      on([EVENT_MOUNTED, EVENT_UPDATED, EVENT_REFRESH], apply(cover, true));
4346    }
4347  }
4348
4349  function cover(cover2) {
4350    Components2.Slides.forEach(function (Slide) {
4351      var img = child(Slide.container || Slide.slide, "img");
4352
4353      if (img && img.src) {
4354        toggle(cover2, img, Slide);
4355      }
4356    });
4357  }
4358
4359  function toggle(cover2, img, Slide) {
4360    Slide.style("background", cover2 ? "center/cover no-repeat url(\"" + img.src + "\")" : "", true);
4361    display(img, cover2 ? "none" : "");
4362  }
4363
4364  return {
4365    mount: mount,
4366    destroy: apply(cover, false)
4367  };
4368}
4369
4370var BOUNCE_DIFF_THRESHOLD = 10;
4371var BOUNCE_DURATION = 600;
4372var FRICTION_FACTOR = 0.6;
4373var BASE_VELOCITY = 1.5;
4374var MIN_DURATION = 800;
4375
4376function Scroll(Splide2, Components2, options) {
4377  var _EventInterface8 = EventInterface(Splide2),
4378      on = _EventInterface8.on,
4379      emit = _EventInterface8.emit;
4380
4381  var set = Splide2.state.set;
4382  var Move = Components2.Move;
4383  var getPosition = Move.getPosition,
4384      getLimit = Move.getLimit,
4385      exceededLimit = Move.exceededLimit,
4386      translate = Move.translate;
4387  var isSlide = Splide2.is(SLIDE);
4388  var interval;
4389  var callback;
4390  var friction = 1;
4391
4392  function mount() {
4393    on(EVENT_MOVE, clear);
4394    on([EVENT_UPDATED, EVENT_REFRESH], cancel);
4395  }
4396
4397  function scroll(destination, duration, snap, onScrolled, noConstrain) {
4398    var from = getPosition();
4399    clear();
4400
4401    if (snap && (!isSlide || !exceededLimit())) {
4402      var size = Components2.Layout.sliderSize();
4403      var offset = sign(destination) * size * floor(abs(destination) / size) || 0;
4404      destination = Move.toPosition(Components2.Controller.toDest(destination % size)) + offset;
4405    }
4406
4407    var noDistance = approximatelyEqual(from, destination, 1);
4408    friction = 1;
4409    duration = noDistance ? 0 : duration || max(abs(destination - from) / BASE_VELOCITY, MIN_DURATION);
4410    callback = onScrolled;
4411    interval = RequestInterval(duration, onEnd, apply(update, from, destination, noConstrain), 1);
4412    set(SCROLLING);
4413    emit(EVENT_SCROLL);
4414    interval.start();
4415  }
4416
4417  function onEnd() {
4418    set(IDLE);
4419    callback && callback();
4420    emit(EVENT_SCROLLED);
4421  }
4422
4423  function update(from, to, noConstrain, rate) {
4424    var position = getPosition();
4425    var target = from + (to - from) * easing(rate);
4426    var diff = (target - position) * friction;
4427    translate(position + diff);
4428
4429    if (isSlide && !noConstrain && exceededLimit()) {
4430      friction *= FRICTION_FACTOR;
4431
4432      if (abs(diff) < BOUNCE_DIFF_THRESHOLD) {
4433        scroll(getLimit(exceededLimit(true)), BOUNCE_DURATION, false, callback, true);
4434      }
4435    }
4436  }
4437
4438  function clear() {
4439    if (interval) {
4440      interval.cancel();
4441    }
4442  }
4443
4444  function cancel() {
4445    if (interval && !interval.isPaused()) {
4446      clear();
4447      onEnd();
4448    }
4449  }
4450
4451  function easing(t) {
4452    var easingFunc = options.easingFunc;
4453    return easingFunc ? easingFunc(t) : 1 - Math.pow(1 - t, 4);
4454  }
4455
4456  return {
4457    mount: mount,
4458    destroy: clear,
4459    scroll: scroll,
4460    cancel: cancel
4461  };
4462}
4463
4464var SCROLL_LISTENER_OPTIONS = {
4465  passive: false,
4466  capture: true
4467};
4468
4469function Drag(Splide2, Components2, options) {
4470  var _EventInterface9 = EventInterface(Splide2),
4471      on = _EventInterface9.on,
4472      emit = _EventInterface9.emit,
4473      bind = _EventInterface9.bind,
4474      unbind = _EventInterface9.unbind;
4475
4476  var state = Splide2.state;
4477  var Move = Components2.Move,
4478      Scroll = Components2.Scroll,
4479      Controller = Components2.Controller,
4480      track = Components2.Elements.track,
4481      reduce = Components2.Media.reduce;
4482  var _Components2$Directio2 = Components2.Direction,
4483      resolve = _Components2$Directio2.resolve,
4484      orient = _Components2$Directio2.orient;
4485  var getPosition = Move.getPosition,
4486      exceededLimit = Move.exceededLimit;
4487  var basePosition;
4488  var baseEvent;
4489  var prevBaseEvent;
4490  var isFree;
4491  var dragging;
4492  var exceeded = false;
4493  var clickPrevented;
4494  var disabled;
4495  var target;
4496
4497  function mount() {
4498    bind(track, POINTER_MOVE_EVENTS, noop, SCROLL_LISTENER_OPTIONS);
4499    bind(track, POINTER_UP_EVENTS, noop, SCROLL_LISTENER_OPTIONS);
4500    bind(track, POINTER_DOWN_EVENTS, onPointerDown, SCROLL_LISTENER_OPTIONS);
4501    bind(track, "click", onClick, {
4502      capture: true
4503    });
4504    bind(track, "dragstart", prevent);
4505    on([EVENT_MOUNTED, EVENT_UPDATED], init);
4506  }
4507
4508  function init() {
4509    var drag = options.drag;
4510    disable(!drag);
4511    isFree = drag === "free";
4512  }
4513
4514  function onPointerDown(e) {
4515    clickPrevented = false;
4516
4517    if (!disabled) {
4518      var isTouch = isTouchEvent(e);
4519
4520      if (isDraggable(e.target) && (isTouch || !e.button)) {
4521        if (!Controller.isBusy()) {
4522          target = isTouch ? track : window;
4523          dragging = state.is([MOVING, SCROLLING]);
4524          prevBaseEvent = null;
4525          bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
4526          bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
4527          Move.cancel();
4528          Scroll.cancel();
4529          save(e);
4530        } else {
4531          prevent(e, true);
4532        }
4533      }
4534    }
4535  }
4536
4537  function onPointerMove(e) {
4538    if (!state.is(DRAGGING)) {
4539      state.set(DRAGGING);
4540      emit(EVENT_DRAG);
4541    }
4542
4543    if (e.cancelable) {
4544      if (dragging) {
4545        Move.translate(basePosition + constrain(diffCoord(e)));
4546        var expired = diffTime(e) > LOG_INTERVAL;
4547        var hasExceeded = exceeded !== (exceeded = exceededLimit());
4548
4549        if (expired || hasExceeded) {
4550          save(e);
4551        }
4552
4553        clickPrevented = true;
4554        emit(EVENT_DRAGGING);
4555        prevent(e);
4556      } else if (isSliderDirection(e)) {
4557        dragging = shouldStart(e);
4558        prevent(e);
4559      }
4560    }
4561  }
4562
4563  function onPointerUp(e) {
4564    if (state.is(DRAGGING)) {
4565      state.set(IDLE);
4566      emit(EVENT_DRAGGED);
4567    }
4568
4569    if (dragging) {
4570      move(e);
4571      prevent(e);
4572    }
4573
4574    unbind(target, POINTER_MOVE_EVENTS, onPointerMove);
4575    unbind(target, POINTER_UP_EVENTS, onPointerUp);
4576    dragging = false;
4577  }
4578
4579  function onClick(e) {
4580    if (!disabled && clickPrevented) {
4581      prevent(e, true);
4582    }
4583  }
4584
4585  function save(e) {
4586    prevBaseEvent = baseEvent;
4587    baseEvent = e;
4588    basePosition = getPosition();
4589  }
4590
4591  function move(e) {
4592    var velocity = computeVelocity(e);
4593    var destination = computeDestination(velocity);
4594    var rewind = options.rewind && options.rewindByDrag;
4595    reduce(false);
4596
4597    if (isFree) {
4598      Controller.scroll(destination, 0, options.snap);
4599    } else if (Splide2.is(FADE)) {
4600      Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
4601    } else if (Splide2.is(SLIDE) && exceeded && rewind) {
4602      Controller.go(exceededLimit(true) ? ">" : "<");
4603    } else {
4604      Controller.go(Controller.toDest(destination), true);
4605    }
4606
4607    reduce(true);
4608  }
4609
4610  function shouldStart(e) {
4611    var thresholds = options.dragMinThreshold;
4612    var isObj = isObject(thresholds);
4613    var mouse = isObj && thresholds.mouse || 0;
4614    var touch = (isObj ? thresholds.touch : +thresholds) || 10;
4615    return abs(diffCoord(e)) > (isTouchEvent(e) ? touch : mouse);
4616  }
4617
4618  function isSliderDirection(e) {
4619    return abs(diffCoord(e)) > abs(diffCoord(e, true));
4620  }
4621
4622  function computeVelocity(e) {
4623    if (Splide2.is(LOOP) || !exceeded) {
4624      var time = diffTime(e);
4625
4626      if (time && time < LOG_INTERVAL) {
4627        return diffCoord(e) / time;
4628      }
4629    }
4630
4631    return 0;
4632  }
4633
4634  function computeDestination(velocity) {
4635    return getPosition() + sign(velocity) * min(abs(velocity) * (options.flickPower || 600), isFree ? Infinity : Components2.Layout.listSize() * (options.flickMaxPages || 1));
4636  }
4637
4638  function diffCoord(e, orthogonal) {
4639    return coordOf(e, orthogonal) - coordOf(getBaseEvent(e), orthogonal);
4640  }
4641
4642  function diffTime(e) {
4643    return timeOf(e) - timeOf(getBaseEvent(e));
4644  }
4645
4646  function getBaseEvent(e) {
4647    return baseEvent === e && prevBaseEvent || baseEvent;
4648  }
4649
4650  function coordOf(e, orthogonal) {
4651    return (isTouchEvent(e) ? e.changedTouches[0] : e)["page" + resolve(orthogonal ? "Y" : "X")];
4652  }
4653
4654  function constrain(diff) {
4655    return diff / (exceeded && Splide2.is(SLIDE) ? FRICTION : 1);
4656  }
4657
4658  function isDraggable(target2) {
4659    var noDrag = options.noDrag;
4660    return !matches(target2, "." + CLASS_PAGINATION_PAGE + ", ." + CLASS_ARROW) && (!noDrag || !matches(target2, noDrag));
4661  }
4662
4663  function isTouchEvent(e) {
4664    return typeof TouchEvent !== "undefined" && e instanceof TouchEvent;
4665  }
4666
4667  function isDragging() {
4668    return dragging;
4669  }
4670
4671  function disable(value) {
4672    disabled = value;
4673  }
4674
4675  return {
4676    mount: mount,
4677    disable: disable,
4678    isDragging: isDragging
4679  };
4680}
4681
4682var NORMALIZATION_MAP = {
4683  Spacebar: " ",
4684  Right: ARROW_RIGHT,
4685  Left: ARROW_LEFT,
4686  Up: ARROW_UP,
4687  Down: ARROW_DOWN
4688};
4689
4690function normalizeKey(key) {
4691  key = isString(key) ? key : key.key;
4692  return NORMALIZATION_MAP[key] || key;
4693}
4694
4695var KEYBOARD_EVENT = "keydown";
4696
4697function Keyboard(Splide2, Components2, options) {
4698  var _EventInterface10 = EventInterface(Splide2),
4699      on = _EventInterface10.on,
4700      bind = _EventInterface10.bind,
4701      unbind = _EventInterface10.unbind;
4702
4703  var root = Splide2.root;
4704  var resolve = Components2.Direction.resolve;
4705  var target;
4706  var disabled;
4707
4708  function mount() {
4709    init();
4710    on(EVENT_UPDATED, destroy);
4711    on(EVENT_UPDATED, init);
4712    on(EVENT_MOVE, onMove);
4713  }
4714
4715  function init() {
4716    var keyboard = options.keyboard;
4717
4718    if (keyboard) {
4719      target = keyboard === "global" ? window : root;
4720      bind(target, KEYBOARD_EVENT, onKeydown);
4721    }
4722  }
4723
4724  function destroy() {
4725    unbind(target, KEYBOARD_EVENT);
4726  }
4727
4728  function disable(value) {
4729    disabled = value;
4730  }
4731
4732  function onMove() {
4733    var _disabled = disabled;
4734    disabled = true;
4735    nextTick(function () {
4736      disabled = _disabled;
4737    });
4738  }
4739
4740  function onKeydown(e) {
4741    if (!disabled) {
4742      var key = normalizeKey(e);
4743
4744      if (key === resolve(ARROW_LEFT)) {
4745        Splide2.go("<");
4746      } else if (key === resolve(ARROW_RIGHT)) {
4747        Splide2.go(">");
4748      }
4749    }
4750  }
4751
4752  return {
4753    mount: mount,
4754    destroy: destroy,
4755    disable: disable
4756  };
4757}
4758
4759var SRC_DATA_ATTRIBUTE = DATA_ATTRIBUTE + "-lazy";
4760var SRCSET_DATA_ATTRIBUTE = SRC_DATA_ATTRIBUTE + "-srcset";
4761var IMAGE_SELECTOR = "[" + SRC_DATA_ATTRIBUTE + "], [" + SRCSET_DATA_ATTRIBUTE + "]";
4762
4763function LazyLoad(Splide2, Components2, options) {
4764  var _EventInterface11 = EventInterface(Splide2),
4765      on = _EventInterface11.on,
4766      off = _EventInterface11.off,
4767      bind = _EventInterface11.bind,
4768      emit = _EventInterface11.emit;
4769
4770  var isSequential = options.lazyLoad === "sequential";
4771  var events = [EVENT_MOVED, EVENT_SCROLLED];
4772  var entries = [];
4773
4774  function mount() {
4775    if (options.lazyLoad) {
4776      init();
4777      on(EVENT_REFRESH, init);
4778    }
4779  }
4780
4781  function init() {
4782    empty(entries);
4783    register();
4784
4785    if (isSequential) {
4786      loadNext();
4787    } else {
4788      off(events);
4789      on(events, check);
4790      check();
4791    }
4792  }
4793
4794  function register() {
4795    Components2.Slides.forEach(function (Slide) {
4796      queryAll(Slide.slide, IMAGE_SELECTOR).forEach(function (img) {
4797        var src = getAttribute(img, SRC_DATA_ATTRIBUTE);
4798        var srcset = getAttribute(img, SRCSET_DATA_ATTRIBUTE);
4799
4800        if (src !== img.src || srcset !== img.srcset) {
4801          var className = options.classes.spinner;
4802          var parent = img.parentElement;
4803          var spinner = child(parent, "." + className) || create("span", className, parent);
4804          entries.push([img, Slide, spinner]);
4805          img.src || display(img, "none");
4806        }
4807      });
4808    });
4809  }
4810
4811  function check() {
4812    entries = entries.filter(function (data) {
4813      var distance = options.perPage * ((options.preloadPages || 1) + 1) - 1;
4814      return data[1].isWithin(Splide2.index, distance) ? load(data) : true;
4815    });
4816    entries.length || off(events);
4817  }
4818
4819  function load(data) {
4820    var img = data[0];
4821    addClass(data[1].slide, CLASS_LOADING);
4822    bind(img, "load error", apply(onLoad, data));
4823    setAttribute(img, "src", getAttribute(img, SRC_DATA_ATTRIBUTE));
4824    setAttribute(img, "srcset", getAttribute(img, SRCSET_DATA_ATTRIBUTE));
4825    removeAttribute(img, SRC_DATA_ATTRIBUTE);
4826    removeAttribute(img, SRCSET_DATA_ATTRIBUTE);
4827  }
4828
4829  function onLoad(data, e) {
4830    var img = data[0],
4831        Slide = data[1];
4832    removeClass(Slide.slide, CLASS_LOADING);
4833
4834    if (e.type !== "error") {
4835      remove(data[2]);
4836      display(img, "");
4837      emit(EVENT_LAZYLOAD_LOADED, img, Slide);
4838      emit(EVENT_RESIZE);
4839    }
4840
4841    isSequential && loadNext();
4842  }
4843
4844  function loadNext() {
4845    entries.length && load(entries.shift());
4846  }
4847
4848  return {
4849    mount: mount,
4850    destroy: apply(empty, entries),
4851    check: check
4852  };
4853}
4854
4855function Pagination(Splide2, Components2, options) {
4856  var event = EventInterface(Splide2);
4857  var on = event.on,
4858      emit = event.emit,
4859      bind = event.bind;
4860  var Slides = Components2.Slides,
4861      Elements = Components2.Elements,
4862      Controller = Components2.Controller;
4863  var hasFocus = Controller.hasFocus,
4864      getIndex = Controller.getIndex,
4865      go = Controller.go;
4866  var resolve = Components2.Direction.resolve;
4867  var placeholder = Elements.pagination;
4868  var items = [];
4869  var list;
4870  var paginationClasses;
4871
4872  function mount() {
4873    destroy();
4874    on([EVENT_UPDATED, EVENT_REFRESH, EVENT_END_INDEX_CHANGED], mount);
4875    var enabled = options.pagination;
4876    placeholder && display(placeholder, enabled ? "" : "none");
4877
4878    if (enabled) {
4879      on([EVENT_MOVE, EVENT_SCROLL, EVENT_SCROLLED], update);
4880      createPagination();
4881      update();
4882      emit(EVENT_PAGINATION_MOUNTED, {
4883        list: list,
4884        items: items
4885      }, getAt(Splide2.index));
4886    }
4887  }
4888
4889  function destroy() {
4890    if (list) {
4891      remove(placeholder ? slice(list.children) : list);
4892      removeClass(list, paginationClasses);
4893      empty(items);
4894      list = null;
4895    }
4896
4897    event.destroy();
4898  }
4899
4900  function createPagination() {
4901    var length = Splide2.length;
4902    var classes = options.classes,
4903        i18n = options.i18n,
4904        perPage = options.perPage;
4905    var max = hasFocus() ? Controller.getEnd() + 1 : ceil(length / perPage);
4906    list = placeholder || create("ul", classes.pagination, Elements.track.parentElement);
4907    addClass(list, paginationClasses = CLASS_PAGINATION + "--" + getDirection());
4908    setAttribute(list, ROLE, "tablist");
4909    setAttribute(list, ARIA_LABEL, i18n.select);
4910    setAttribute(list, ARIA_ORIENTATION, getDirection() === TTB ? "vertical" : "");
4911
4912    for (var i = 0; i < max; i++) {
4913      var li = create("li", null, list);
4914      var button = create("button", {
4915        class: classes.page,
4916        type: "button"
4917      }, li);
4918      var controls = Slides.getIn(i).map(function (Slide) {
4919        return Slide.slide.id;
4920      });
4921      var text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
4922      bind(button, "click", apply(onClick, i));
4923
4924      if (options.paginationKeyboard) {
4925        bind(button, "keydown", apply(onKeydown, i));
4926      }
4927
4928      setAttribute(li, ROLE, "presentation");
4929      setAttribute(button, ROLE, "tab");
4930      setAttribute(button, ARIA_CONTROLS, controls.join(" "));
4931      setAttribute(button, ARIA_LABEL, format(text, i + 1));
4932      setAttribute(button, TAB_INDEX, -1);
4933      items.push({
4934        li: li,
4935        button: button,
4936        page: i
4937      });
4938    }
4939  }
4940
4941  function onClick(page) {
4942    go(">" + page, true);
4943  }
4944
4945  function onKeydown(page, e) {
4946    var length = items.length;
4947    var key = normalizeKey(e);
4948    var dir = getDirection();
4949    var nextPage = -1;
4950
4951    if (key === resolve(ARROW_RIGHT, false, dir)) {
4952      nextPage = ++page % length;
4953    } else if (key === resolve(ARROW_LEFT, false, dir)) {
4954      nextPage = (--page + length) % length;
4955    } else if (key === "Home") {
4956      nextPage = 0;
4957    } else if (key === "End") {
4958      nextPage = length - 1;
4959    }
4960
4961    var item = items[nextPage];
4962
4963    if (item) {
4964      focus(item.button);
4965      go(">" + nextPage);
4966      prevent(e, true);
4967    }
4968  }
4969
4970  function getDirection() {
4971    return options.paginationDirection || options.direction;
4972  }
4973
4974  function getAt(index) {
4975    return items[Controller.toPage(index)];
4976  }
4977
4978  function update() {
4979    var prev = getAt(getIndex(true));
4980    var curr = getAt(getIndex());
4981
4982    if (prev) {
4983      var button = prev.button;
4984      removeClass(button, CLASS_ACTIVE);
4985      removeAttribute(button, ARIA_SELECTED);
4986      setAttribute(button, TAB_INDEX, -1);
4987    }
4988
4989    if (curr) {
4990      var _button = curr.button;
4991      addClass(_button, CLASS_ACTIVE);
4992      setAttribute(_button, ARIA_SELECTED, true);
4993      setAttribute(_button, TAB_INDEX, "");
4994    }
4995
4996    emit(EVENT_PAGINATION_UPDATED, {
4997      list: list,
4998      items: items
4999    }, prev, curr);
5000  }
5001
5002  return {
5003    items: items,
5004    mount: mount,
5005    destroy: destroy,
5006    getAt: getAt,
5007    update: update
5008  };
5009}
5010
5011var TRIGGER_KEYS = [" ", "Enter"];
5012
5013function Sync(Splide2, Components2, options) {
5014  var isNavigation = options.isNavigation,
5015      slideFocus = options.slideFocus;
5016  var events = [];
5017
5018  function mount() {
5019    Splide2.splides.forEach(function (target) {
5020      if (!target.isParent) {
5021        sync(Splide2, target.splide);
5022        sync(target.splide, Splide2);
5023      }
5024    });
5025
5026    if (isNavigation) {
5027      navigate();
5028    }
5029  }
5030
5031  function destroy() {
5032    events.forEach(function (event) {
5033      event.destroy();
5034    });
5035    empty(events);
5036  }
5037
5038  function remount() {
5039    destroy();
5040    mount();
5041  }
5042
5043  function sync(splide, target) {
5044    var event = EventInterface(splide);
5045    event.on(EVENT_MOVE, function (index, prev, dest) {
5046      target.go(target.is(LOOP) ? dest : index);
5047    });
5048    events.push(event);
5049  }
5050
5051  function navigate() {
5052    var event = EventInterface(Splide2);
5053    var on = event.on;
5054    on(EVENT_CLICK, onClick);
5055    on(EVENT_SLIDE_KEYDOWN, onKeydown);
5056    on([EVENT_MOUNTED, EVENT_UPDATED], update);
5057    events.push(event);
5058    event.emit(EVENT_NAVIGATION_MOUNTED, Splide2.splides);
5059  }
5060
5061  function update() {
5062    setAttribute(Components2.Elements.list, ARIA_ORIENTATION, options.direction === TTB ? "vertical" : "");
5063  }
5064
5065  function onClick(Slide) {
5066    Splide2.go(Slide.index);
5067  }
5068
5069  function onKeydown(Slide, e) {
5070    if (includes(TRIGGER_KEYS, normalizeKey(e))) {
5071      onClick(Slide);
5072      prevent(e);
5073    }
5074  }
5075
5076  return {
5077    setup: apply(Components2.Media.set, {
5078      slideFocus: isUndefined(slideFocus) ? isNavigation : slideFocus
5079    }, true),
5080    mount: mount,
5081    destroy: destroy,
5082    remount: remount
5083  };
5084}
5085
5086function Wheel(Splide2, Components2, options) {
5087  var _EventInterface12 = EventInterface(Splide2),
5088      bind = _EventInterface12.bind;
5089
5090  var lastTime = 0;
5091
5092  function mount() {
5093    if (options.wheel) {
5094      bind(Components2.Elements.track, "wheel", onWheel, SCROLL_LISTENER_OPTIONS);
5095    }
5096  }
5097
5098  function onWheel(e) {
5099    if (e.cancelable) {
5100      var deltaY = e.deltaY;
5101      var backwards = deltaY < 0;
5102      var timeStamp = timeOf(e);
5103
5104      var _min = options.wheelMinThreshold || 0;
5105
5106      var sleep = options.wheelSleep || 0;
5107
5108      if (abs(deltaY) > _min && timeStamp - lastTime > sleep) {
5109        Splide2.go(backwards ? "<" : ">");
5110        lastTime = timeStamp;
5111      }
5112
5113      shouldPrevent(backwards) && prevent(e);
5114    }
5115  }
5116
5117  function shouldPrevent(backwards) {
5118    return !options.releaseWheel || Splide2.state.is(MOVING) || Components2.Controller.getAdjacent(backwards) !== -1;
5119  }
5120
5121  return {
5122    mount: mount
5123  };
5124}
5125
5126var SR_REMOVAL_DELAY = 90;
5127
5128function Live(Splide2, Components2, options) {
5129  var _EventInterface13 = EventInterface(Splide2),
5130      on = _EventInterface13.on;
5131
5132  var track = Components2.Elements.track;
5133  var enabled = options.live && !options.isNavigation;
5134  var sr = create("span", CLASS_SR);
5135  var interval = RequestInterval(SR_REMOVAL_DELAY, apply(toggle, false));
5136
5137  function mount() {
5138    if (enabled) {
5139      disable(!Components2.Autoplay.isPaused());
5140      setAttribute(track, ARIA_ATOMIC, true);
5141      sr.textContent = "\u2026";
5142      on(EVENT_AUTOPLAY_PLAY, apply(disable, true));
5143      on(EVENT_AUTOPLAY_PAUSE, apply(disable, false));
5144      on([EVENT_MOVED, EVENT_SCROLLED], apply(toggle, true));
5145    }
5146  }
5147
5148  function toggle(active) {
5149    setAttribute(track, ARIA_BUSY, active);
5150
5151    if (active) {
5152      append(track, sr);
5153      interval.start();
5154    } else {
5155      remove(sr);
5156      interval.cancel();
5157    }
5158  }
5159
5160  function destroy() {
5161    removeAttribute(track, [ARIA_LIVE, ARIA_ATOMIC, ARIA_BUSY]);
5162    remove(sr);
5163  }
5164
5165  function disable(disabled) {
5166    if (enabled) {
5167      setAttribute(track, ARIA_LIVE, disabled ? "off" : "polite");
5168    }
5169  }
5170
5171  return {
5172    mount: mount,
5173    disable: disable,
5174    destroy: destroy
5175  };
5176}
5177
5178var ComponentConstructors = /*#__PURE__*/Object.freeze({
5179  __proto__: null,
5180  Media: Media,
5181  Direction: Direction,
5182  Elements: Elements,
5183  Slides: Slides,
5184  Layout: Layout,
5185  Clones: Clones,
5186  Move: Move,
5187  Controller: Controller,
5188  Arrows: Arrows,
5189  Autoplay: Autoplay,
5190  Cover: Cover,
5191  Scroll: Scroll,
5192  Drag: Drag,
5193  Keyboard: Keyboard,
5194  LazyLoad: LazyLoad,
5195  Pagination: Pagination,
5196  Sync: Sync,
5197  Wheel: Wheel,
5198  Live: Live
5199});
5200var I18N = {
5201  prev: "Previous slide",
5202  next: "Next slide",
5203  first: "Go to first slide",
5204  last: "Go to last slide",
5205  slideX: "Go to slide %s",
5206  pageX: "Go to page %s",
5207  play: "Start autoplay",
5208  pause: "Pause autoplay",
5209  carousel: "carousel",
5210  slide: "slide",
5211  select: "Select a slide to show",
5212  slideLabel: "%s of %s"
5213};
5214var DEFAULTS = {
5215  type: "slide",
5216  role: "region",
5217  speed: 400,
5218  perPage: 1,
5219  cloneStatus: true,
5220  arrows: true,
5221  pagination: true,
5222  paginationKeyboard: true,
5223  interval: 5e3,
5224  pauseOnHover: true,
5225  pauseOnFocus: true,
5226  resetProgress: true,
5227  easing: "cubic-bezier(0.25, 1, 0.5, 1)",
5228  drag: true,
5229  direction: "ltr",
5230  trimSpace: true,
5231  focusableNodes: "a, button, textarea, input, select, iframe",
5232  live: true,
5233  classes: CLASSES,
5234  i18n: I18N,
5235  reducedMotion: {
5236    speed: 0,
5237    rewindSpeed: 0,
5238    autoplay: "pause"
5239  }
5240};
5241
5242function Fade(Splide2, Components2, options) {
5243  var Slides = Components2.Slides;
5244
5245  function mount() {
5246    EventInterface(Splide2).on([EVENT_MOUNTED, EVENT_REFRESH], init);
5247  }
5248
5249  function init() {
5250    Slides.forEach(function (Slide) {
5251      Slide.style("transform", "translateX(-" + 100 * Slide.index + "%)");
5252    });
5253  }
5254
5255  function start(index, done) {
5256    Slides.style("transition", "opacity " + options.speed + "ms " + options.easing);
5257    nextTick(done);
5258  }
5259
5260  return {
5261    mount: mount,
5262    start: start,
5263    cancel: noop
5264  };
5265}
5266
5267function Slide(Splide2, Components2, options) {
5268  var Move = Components2.Move,
5269      Controller = Components2.Controller,
5270      Scroll = Components2.Scroll;
5271  var list = Components2.Elements.list;
5272  var transition = apply(style, list, "transition");
5273  var endCallback;
5274
5275  function mount() {
5276    EventInterface(Splide2).bind(list, "transitionend", function (e) {
5277      if (e.target === list && endCallback) {
5278        cancel();
5279        endCallback();
5280      }
5281    });
5282  }
5283
5284  function start(index, done) {
5285    var destination = Move.toPosition(index, true);
5286    var position = Move.getPosition();
5287    var speed = getSpeed(index);
5288
5289    if (abs(destination - position) >= 1 && speed >= 1) {
5290      if (options.useScroll) {
5291        Scroll.scroll(destination, speed, false, done);
5292      } else {
5293        transition("transform " + speed + "ms " + options.easing);
5294        Move.translate(destination, true);
5295        endCallback = done;
5296      }
5297    } else {
5298      Move.jump(index);
5299      done();
5300    }
5301  }
5302
5303  function cancel() {
5304    transition("");
5305    Scroll.cancel();
5306  }
5307
5308  function getSpeed(index) {
5309    var rewindSpeed = options.rewindSpeed;
5310
5311    if (Splide2.is(SLIDE) && rewindSpeed) {
5312      var prev = Controller.getIndex(true);
5313      var end = Controller.getEnd();
5314
5315      if (prev === 0 && index >= end || prev >= end && index === 0) {
5316        return rewindSpeed;
5317      }
5318    }
5319
5320    return options.speed;
5321  }
5322
5323  return {
5324    mount: mount,
5325    start: start,
5326    cancel: cancel
5327  };
5328}
5329
5330var _Splide = /*#__PURE__*/function () {
5331  function _Splide(target, options) {
5332    this.event = EventInterface();
5333    this.Components = {};
5334    this.state = State(CREATED);
5335    this.splides = [];
5336    this._o = {};
5337    this._E = {};
5338    var root = isString(target) ? query(document, target) : target;
5339    assert(root, root + " is invalid.");
5340    this.root = root;
5341    options = merge({
5342      label: getAttribute(root, ARIA_LABEL) || "",
5343      labelledby: getAttribute(root, ARIA_LABELLEDBY) || ""
5344    }, DEFAULTS, _Splide.defaults, options || {});
5345
5346    try {
5347      merge(options, JSON.parse(getAttribute(root, DATA_ATTRIBUTE)));
5348    } catch (e) {
5349      assert(false, "Invalid JSON");
5350    }
5351
5352    this._o = Object.create(merge({}, options));
5353  }
5354
5355  var _proto = _Splide.prototype;
5356
5357  _proto.mount = function mount(Extensions, Transition) {
5358    var _this = this;
5359
5360    var state = this.state,
5361        Components2 = this.Components;
5362    assert(state.is([CREATED, DESTROYED]), "Already mounted!");
5363    state.set(CREATED);
5364    this._C = Components2;
5365    this._T = Transition || this._T || (this.is(FADE) ? Fade : Slide);
5366    this._E = Extensions || this._E;
5367    var Constructors = assign
vendor: 2,918 bytes, lines 5367-5506
5367$1({}, ComponentConstructors, this._E, {
5368      Transition: this._T
5369    });
5370    forOwn(Constructors, function (Component, key) {
5371      var component = Component(_this, Components2, _this._o);
5372      Components2[key] = component;
5373      component.setup && component.setup();
5374    });
5375    forOwn(Components2, function (component) {
5376      component.mount && component.mount();
5377    });
5378    this.emit(EVENT_MOUNTED);
5379    addClass(this.root, CLASS_INITIALIZED);
5380    state.set(IDLE);
5381    this.emit(EVENT_READY);
5382    return this;
5383  };
5384
5385  _proto.sync = function sync(splide) {
5386    this.splides.push({
5387      splide: splide
5388    });
5389    splide.splides.push({
5390      splide: this,
5391      isParent: true
5392    });
5393
5394    if (this.state.is(IDLE)) {
5395      this._C.Sync.remount();
5396
5397      splide.Components.Sync.remount();
5398    }
5399
5400    return this;
5401  };
5402
5403  _proto.go = function go(control) {
5404    this._C.Controller.go(control);
5405
5406    return this;
5407  };
5408
5409  _proto.on = function on(events, callback) {
5410    this.event.on(events, callback);
5411    return this;
5412  };
5413
5414  _proto.off = function off(events) {
5415    this.event.off(events);
5416    return this;
5417  };
5418
5419  _proto.emit = function emit(event) {
5420    var _this$event;
5421
5422    (_this$event = this.event).emit.apply(_this$event, [event].concat(slice(arguments, 1)));
5423
5424    return this;
5425  };
5426
5427  _proto.add = function add(slides, index) {
5428    this._C.Slides.add(slides, index);
5429
5430    return this;
5431  };
5432
5433  _proto.remove = function remove(matcher) {
5434    this._C.Slides.remove(matcher);
5435
5436    return this;
5437  };
5438
5439  _proto.is = function is(type) {
5440    return this._o.type === type;
5441  };
5442
5443  _proto.refresh = function refresh() {
5444    this.emit(EVENT_REFRESH);
5445    return this;
5446  };
5447
5448  _proto.destroy = function destroy(completely) {
5449    if (completely === void 0) {
5450      completely = true;
5451    }
5452
5453    var event = this.event,
5454        state = this.state;
5455
5456    if (state.is(CREATED)) {
5457      EventInterface(this).on(EVENT_READY, this.destroy.bind(this, completely));
5458    } else {
5459      forOwn(this._C, function (component) {
5460        component.destroy && component.destroy(completely);
5461      }, true);
5462      event.emit(EVENT_DESTROY);
5463      event.destroy();
5464      completely && empty(this.splides);
5465      state.set(DESTROYED);
5466    }
5467
5468    return this;
5469  };
5470
5471  _createClass(_Splide, [{
5472    key: "options",
5473    get: function get() {
5474      return this._o;
5475    },
5476    set: function set(options) {
5477      this._C.Media.set(options, true, true);
5478    }
5479  }, {
5480    key: "length",
5481    get: function get() {
5482      return this._C.Slides.getLength(true);
5483    }
5484  }, {
5485    key: "index",
5486    get: function get() {
5487      return this._C.Controller.getIndex();
5488    }
5489  }]);
5490
5491  return _Splide;
5492}();
5493
5494var Splide = _Splide;
5495Splide.defaults = {};
5496Splide.STATES = STATES;
5497
5498class CsSplide extends Core {
5499
5500  static options = {
5501    sliderConfigBase: {
5502      type: 'slide',
5503      lazyLoad: 'nearby'
5504    },
5505    sliderMultipleItemsConfig: {
5506      perPage: 5,
5507      arrows: false,
5508      gap: '60px',
5509      breakpoints: {
5510        1160: {
5511          gap: '30px'
5512        },
5513        920: {
5514          perPage: 5
5515        },
5516        620: {
5517          perPage: 3
5518        },
5519        420: {
5520          perPage: 2
5521        }
5522      },
5523    },
5524    sliderHomeConfig: {
5525      arrows: false,
5526      autoplay: true,
5527    },
5528    slider3ItemsConfig: {
5529      perPage: 3,
5530      gap: '32px',
5531      breakpoints: {
5532        620: {
5533          perPage: 2
5534        },
5535        420: {
5536          perPage: 1
5537        }
5538      },
5539    },
5540    slider3LargeItemsConfig: {
5541      perPage: 3,
5542      gap: '32px',
5543      breakpoints: {
5544        920: {
5545          perPage: 2
5546        },
5547        620: {
5548          perPage: 1
5549        }
5550      },
5551    },
5552    sliderDefaultI18n: {
5553      i18n: {
5554        prev: 'Previous Slide',
5555        next: 'Next Slide',
5556        first: 'Go to the first slide',
5557        last: 'Go to the last slide',
5558        slideX: 'Go to slide %s',
5559        pageX: 'Go to page %s',
5560        play: 'Start autoplay',
5561        pause: 'Pause autoplay',
5562        carousel: 'Slider',
5563        slide: 'Slide',
5564        select: 'Select a slide to display',
5565        slideLabel: '%s of %s'
5566      }
5567    }
5568  };
5569
5570  initializeSplide(options = {}) {
5571    const splide = new Splide(this.el, options);
5572    splide.on('mounted', () => {
5573      this.startStopAnimation(splide);
5574    });
5575    splide.mount();
5576  }
5577
5578  startStopAnimation(splide) {
5579    const autoplay = splide.Components.Autoplay;
5580
5581    // Check initial state: if animations are already disabled (e.g. cookie was set before slider mounted)
5582    if (document.body.classList.contains('h-stop-animations')) {
5583      autoplay.pause();
5584    }
5585
5586    // Listen for future toggle events from the accessibility animation
5587    document.addEventListener('splideSlidersFound', function(e) {
5588      if (e.detail.found === true) {
5589        if (autoplay.isPaused()) {
5590          autoplay.play();
5591        }
5592      } else {
5593        autoplay.pause();
5594      }
5595    });
5596  }
5597
5598  init() {
5599    let sliderOptions = CsSplide.options.sliderConfigBase;
5600
5601    try {
5602      const langConfig = TranslatedLang.translatedLangConfig('splide', 'i18n');
5603      sliderOptions = { ...sliderOptions, ...langConfig };
5604    } catch {
5605      sliderOptions = { ...sliderOptions, ...CsSplide.options.sliderDefaultI18n };
5606    }
5607
5608    if (this.el.matches('.js-splide-slider--multiple-items')) {
5609      sliderOptions = { ...sliderOptions, ...CsSplide.options.sliderMultipleItemsConfig };
5610      this.initializeSplide(sliderOptions);
5611    } else if (this.el.matches('.js-splide-slider--3-items')) {
5612      sliderOptions = { ...sliderOptions, ...CsSplide.options.slider3ItemsConfig };
5613      this.initializeSplide(sliderOptions);
5614    } else if (this.el.matches('.js-splide-slider--home')) {
5615      sliderOptions = { ...sliderOptions, ...CsSplide.options.sliderHomeConfig };
5616      this.initializeSplide(sliderOptions);
5617    } else if (this.el.matches('.js-splide-slider--3-large-items')) {
5618      sliderOptions = { ...sliderOptions, ...CsSplide.options.slider3LargeItemsConfig };
5619      this.initializeSplide(sliderOptions);
5620    } else {
5621      sliderOptions = { ...sliderOptions };
5622      this.initializeSplide(sliderOptions);
5623    }
5624  }
5625}
5626
5627class CsResponsiveVideo extends Core {
5628
5629  static options = {
5630
5631  };
5632
5633  /**
5634   * init responsive video
5635   */
5636  initResponsiveVideo() {
5637    const videos = document.querySelectorAll('.js-responsive-video');
5638
5639    const setSources = (video) => {
5640
5641      const source = video.querySelector('source');
5642      const { small, large } = video.dataset;
5643
5644      let newSrc = '';
5645
5646      /* @ToDo FE: use breakpoints from css config */
5647      if (window.innerWidth <= 820) {
5648        newSrc = small;
5649      } else {
5650        newSrc = large;
5651      }
5652
5653      if (source.getAttribute('src') !== newSrc) {
5654        source.setAttribute('src', newSrc);
5655        video.load();
5656      }
5657    };
5658
5659    const handleResize = () => {
5660      videos.forEach(video => {
5661        setSources(video);
5662      });
5663    };
5664
5665    videos.forEach(video => {
5666      setSources(video);
5667    });
5668
5669    let resizeTimeout;
5670    window.addEventListener('resize', () => {
5671      clearTimeout(resizeTimeout);
5672      resizeTimeout = setTimeout(() => {
5673        handleResize();
5674      }, 100);
5675
5676    });
5677  }
5678
5679  init() {
5680    this.initResponsiveVideo();
5681  }
5682}
5683
5684function _extends() {
5685  return _extends = Object.assign ? Object.assign.bind() : function (n) {
5686    for (var e = 1; e < arguments.length; e++) {
5687      var t = arguments[e];
5688      for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
5689    }
5690    return n;
5691  }, _extends.apply(null, arguments);
5692}
5693
vendor: 10,807 bytes, lines 5694-5999
5694class ImageType {
5695  constructor() {
5696    this.figcaptionId = 0;
5697    this.userSettings = null;
5698  }
5699  init(el, container, userSettings) {
5700    this.userSettings = userSettings;
5701    const FIGURE = document.createElement('figure');
5702    const IMAGE = document.createElement('img');
5703    const THUMBNAIL = el.querySelector('img');
5704    const LOADING_INDICATOR = document.createElement('div');
5705
5706    // Accessibility: allow setting focus programmatically on figure elements.
5707    FIGURE.tabIndex = -1;
5708
5709    // Add role="group" to figure
5710    FIGURE.setAttribute('role', 'group');
5711
5712    // Hide figure until the image is loaded
5713    FIGURE.style.opacity = '0';
5714    if (THUMBNAIL) {
5715      IMAGE.alt = THUMBNAIL.alt || '';
5716    }
5717    IMAGE.setAttribute('data-src', el.href);
5718    if (el.hasAttribute('data-srcset')) {
5719      IMAGE.setAttribute('data-srcset', el.getAttribute('data-srcset'));
5720    }
5721    if (el.hasAttribute('data-sizes')) {
5722      IMAGE.setAttribute('data-sizes', el.getAttribute('data-sizes'));
5723    }
5724
5725    // Add image to figure
5726    FIGURE.appendChild(IMAGE);
5727
5728    // Create figcaption
5729    let captionContent;
5730    if (typeof this.userSettings.captionText === 'function') {
5731      captionContent = this.userSettings.captionText(el);
5732    } else if (this.userSettings.captionsSelector === 'self' && el.hasAttribute(this.userSettings.captionAttribute)) {
5733      captionContent = el.getAttribute(this.userSettings.captionAttribute);
5734    } else if (this.userSettings.captionsSelector === 'img' && THUMBNAIL && THUMBNAIL.hasAttribute(this.userSettings.captionAttribute)) {
5735      captionContent = THUMBNAIL.getAttribute(this.userSettings.captionAttribute);
5736    }
5737    if (this.userSettings.captions && captionContent) {
5738      const FIGCAPTION = document.createElement('figcaption');
5739      FIGCAPTION.id = `tobii-figcaption-${this.figcaptionId}`;
5740      const SPAN = document.createElement('span');
5741      if (this.userSettings.captionHTML) {
5742        SPAN.innerHTML = captionContent;
5743      } else {
5744        SPAN.textContent = captionContent;
5745      }
5746      FIGCAPTION.appendChild(SPAN);
5747      if (this.userSettings.captionToggle) {
5748        const isMobile = window.innerWidth < 768;
5749        const BUTTON = document.createElement('button');
5750        BUTTON.className = 'caption-toggle';
5751        BUTTON.textContent = BUTTON.title = this.userSettings.captionToggleLabel[isMobile ? 1 : 0];
5752        BUTTON.setAttribute('aria-controls', FIGCAPTION.id);
5753        BUTTON.setAttribute('aria-expanded', !isMobile);
5754        if (isMobile) {
5755          FIGCAPTION.classList.add('caption-hidden');
5756        }
5757        SPAN.setAttribute('aria-hidden', isMobile);
5758        const preventAndStopEvent = event => {
5759          event.preventDefault();
5760          event.stopPropagation();
5761        };
5762        BUTTON.addEventListener('pointerdown', event => preventAndStopEvent(event));
5763        BUTTON.addEventListener('pointerup', event => preventAndStopEvent(event));
5764        BUTTON.addEventListener('click', event => {
5765          preventAndStopEvent(event);
5766          const isExpanded = BUTTON.getAttribute('aria-expanded') === 'true';
5767          const buttonLabel = isExpanded ? this.userSettings.captionToggleLabel[1] : this.userSettings.captionToggleLabel[0];
5768          BUTTON.textContent = BUTTON.title = buttonLabel;
5769          BUTTON.setAttribute('aria-expanded', !isExpanded);
5770          FIGCAPTION.classList.toggle('caption-hidden');
5771          SPAN.setAttribute('aria-hidden', isExpanded);
5772        });
5773        FIGCAPTION.appendChild(BUTTON);
5774      }
5775      FIGURE.appendChild(FIGCAPTION);
5776      IMAGE.setAttribute('aria-labelledby', FIGCAPTION.id);
5777
5778      // Add aria-label to the figure containing the caption content
5779      FIGURE.setAttribute('aria-label', SPAN.textContent);
5780      ++this.figcaptionId;
5781    }
5782
5783    // Add figure to container
5784    container.appendChild(FIGURE);
5785
5786    // Create loading indicator
5787    LOADING_INDICATOR.className = 'tobii__loader';
5788    LOADING_INDICATOR.setAttribute('role', 'progressbar');
5789    LOADING_INDICATOR.setAttribute('aria-label', this.userSettings.loadingIndicatorLabel);
5790
5791    // Add loading indicator to container
5792    container.appendChild(LOADING_INDICATOR);
5793
5794    // Register type
5795    container.setAttribute('data-type', 'image');
5796    container.classList.add('tobii-image');
5797  }
5798  onPreload(container) {
5799    // Same as preload
5800    this.onLoad(container);
5801  }
5802  onLoad(container) {
5803    const IMAGE = container.querySelector('img');
5804    if (!IMAGE.hasAttribute('data-src')) {
5805      return;
5806    }
5807    const FIGURE = container.querySelector('figure');
5808    const LOADING_INDICATOR = container.querySelector('.tobii__loader');
5809    const handleImageEvent = () => {
5810      container.removeChild(LOADING_INDICATOR);
5811      FIGURE.style.opacity = '1';
5812    };
5813    IMAGE.addEventListener('load', handleImageEvent);
5814    IMAGE.addEventListener('error', handleImageEvent);
5815    if (IMAGE.hasAttribute('data-srcset')) {
5816      IMAGE.setAttribute('srcset', IMAGE.getAttribute('data-srcset'));
5817      IMAGE.removeAttribute('data-srcset');
5818    }
5819    if (IMAGE.hasAttribute('data-sizes')) {
5820      IMAGE.setAttribute('sizes', IMAGE.getAttribute('data-sizes'));
5821      IMAGE.removeAttribute('data-sizes');
5822    }
5823    IMAGE.setAttribute('src', IMAGE.getAttribute('data-src'));
5824    IMAGE.removeAttribute('data-src');
5825  }
5826  onLeave(container) {
5827    // Nothing
5828  }
5829  onCleanup(container) {
5830    // Nothing
5831  }
5832  onReset() {
5833    this.figcaptionId = 0;
5834  }
5835}
5836
5837class IframeType {
5838  constructor() {
5839    this.userSettings = null;
5840  }
5841  init(el, container, userSettings) {
5842    this.userSettings = userSettings;
5843    const HREF = el.hasAttribute('data-target') ? el.getAttribute('data-target') : el.getAttribute('href');
5844    container.setAttribute('data-HREF', HREF);
5845    if (el.hasAttribute('data-allow')) {
5846      container.setAttribute('data-allow', el.getAttribute('data-allow'));
5847    }
5848    if (el.hasAttribute('data-width')) {
5849      container.setAttribute('data-width', `${el.getAttribute('data-width')}`);
5850    }
5851    if (el.hasAttribute('data-height')) {
5852      container.setAttribute('data-height', `${el.getAttribute('data-height')}`);
5853    }
5854
5855    // dont create empty iframes here - very slow
5856
5857    // Register type
5858    container.setAttribute('data-type', 'iframe');
5859    container.classList.add('tobii-iframe');
5860  }
5861  onPreload(container) {
5862    // Nothing
5863  }
5864  onLoad(container) {
5865    let IFRAME = container.querySelector('iframe');
5866
5867    // Create loading indicator
5868    const LOADING_INDICATOR = document.createElement('div');
5869    LOADING_INDICATOR.className = 'tobii__loader';
5870    LOADING_INDICATOR.setAttribute('role', 'progressbar');
5871    LOADING_INDICATOR.setAttribute('aria-label', this.userSettings.loadingIndicatorLabel);
5872    container.appendChild(LOADING_INDICATOR);
5873    if (IFRAME == null) {
5874      // create iframe
5875      IFRAME = document.createElement('iframe');
5876      const HREF = container.getAttribute('data-href');
5877      IFRAME.setAttribute('frameborder', '0');
5878      IFRAME.setAttribute('src', HREF);
5879
5880      // Set allow parameters
5881      let allowValue = 'fullscreen';
5882      if (HREF.includes('youtube.com')) {
5883        allowValue += '; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
5884      } else if (HREF.includes('vimeo.com')) {
5885        allowValue += '; autoplay; picture-in-picture';
5886      } else if (container.hasAttribute('data-allow')) {
5887        allowValue = container.getAttribute('data-allow');
5888      }
5889      IFRAME.setAttribute('allow', allowValue);
5890      if (container.hasAttribute('data-width')) {
5891        IFRAME.style.maxWidth = `${container.getAttribute('data-width')}`;
5892      }
5893      if (container.hasAttribute('data-height')) {
5894        IFRAME.style.maxHeight = `${container.getAttribute('data-height')}`;
5895      }
5896
5897      // Hide until loaded
5898      IFRAME.style.opacity = '0';
5899
5900      // Add iframe to container
5901      container.appendChild(IFRAME);
5902
5903      // Handle load and error
5904      const removeLoader = () => {
5905        IFRAME.style.opacity = '1';
5906        const LOADING_INDICATOR = container.querySelector('.tobii__loader');
5907        if (LOADING_INDICATOR) container.removeChild(LOADING_INDICATOR);
5908      };
5909      IFRAME.addEventListener('load', removeLoader);
5910      IFRAME.addEventListener('error', removeLoader);
5911    } else {
5912      // was already created
5913      IFRAME.setAttribute('src', container.getAttribute('data-href'));
5914    }
5915  }
5916  onLeave(container) {
5917    // Nothing
5918  }
5919  onCleanup(container) {
5920    const IFRAME = container.querySelector('iframe');
5921    IFRAME.removeAttribute('src');
5922    IFRAME.style.opacity = '0';
5923  }
5924  onReset() {
5925    // Nothing
5926  }
5927}
5928
5929class HtmlType {
5930  constructor() {
5931    this.userSettings = null;
5932  }
5933  init(el, container, userSettings) {
5934    this.userSettings = userSettings;
5935    const TARGET_SELECTOR = el.hasAttribute('data-target') ? el.getAttribute('data-target') : el.getAttribute('href');
5936    const TARGET = document.querySelector(TARGET_SELECTOR);
5937    if (!TARGET) {
5938      throw new Error(`Ups, I can't find the target ${TARGET_SELECTOR}.`);
5939    }
5940
5941    // Add content to container
5942    container.appendChild(TARGET);
5943
5944    // Register type
5945    container.setAttribute('data-type', 'html');
5946    container.classList.add('tobii-html');
5947  }
5948  onPreload(container) {
5949    // Nothing
5950  }
5951  onLoad(container, group) {
5952    const VIDEO = container.querySelector('video');
5953    if (VIDEO) {
5954      if (VIDEO.hasAttribute('data-time') && VIDEO.readyState > 0) {
5955        // Continue where video was stopped
5956        VIDEO.currentTime = VIDEO.getAttribute('data-time');
5957      }
5958
5959      // Start playback (and loading if necessary)
5960      VIDEO.play();
5961    }
5962    const audio = container.querySelector('audio');
5963    if (audio) {
5964      // Start playback (and loading if necessary)
5965      audio.play();
5966    }
5967    container.classList.add('tobii-group-' + group);
5968  }
5969  onLeave(container) {
5970    const VIDEO = container.querySelector('video');
5971    if (VIDEO) {
5972      if (!VIDEO.paused) {
5973        // Stop if video is playing
5974        VIDEO.pause();
5975      }
5976
5977      // Backup currentTime (needed for revisit)
5978      if (VIDEO.readyState > 0) {
5979        VIDEO.setAttribute('data-time', VIDEO.currentTime);
5980      }
5981    }
5982    const audio = container.querySelector('audio');
5983    if (audio) {
5984      if (!audio.paused) {
5985        // Stop if is playing
5986        audio.pause();
5987      }
5988    }
5989  }
5990  onCleanup(container) {
5991    const VIDEO = container.querySelector('video');
5992    if (VIDEO) {
5993      if (VIDEO.readyState > 0 && VIDEO.readyState < 3 && VIDEO.duration !== VIDEO.currentTime) {
5994        // Some data has been loaded but not the whole package.
5995        // In order to save bandwidth, stop downloading as soon as possible.
5996        const VIDEO_CLONE = VIDEO.cloneNode(true);
5997        this._removeSources(VIDEO);
5998        VIDEO.load();
5999        VIDEO.parentNode.removeChild(VIDEO);
vendor: 14,366 bytes, lines 6000-6437
6000        container.appendChild(VIDEO_CLONE);
6001      }
6002    }
6003  }
6004  onReset() {
6005    // Nothing
6006  }
6007
6008  /**
6009   * Remove all `src` attributes
6010   *
6011   * @param {HTMLElement} el - Element to remove all `src` attributes
6012   */
6013  _removeSources(el) {
6014    const SOURCES = el.querySelectorAll('src');
6015    if (SOURCES) {
6016      SOURCES.forEach(source => {
6017        source.removeAttribute('src');
6018      });
6019    }
6020  }
6021}
6022
6023class YoutubeType {
6024  constructor() {
6025    this.playerId = 0;
6026    this.PLAYER = [];
6027    this.userSettings = null;
6028  }
6029  init(el, container, userSettings) {
6030    this.userSettings = userSettings;
6031    const IFRAME_PLACEHOLDER = document.createElement('div');
6032
6033    // Add iframePlaceholder to container
6034    container.appendChild(IFRAME_PLACEHOLDER);
6035    this.PLAYER[this.playerId] = new window.YT.Player(IFRAME_PLACEHOLDER, {
6036      host: 'https://www.youtube-nocookie.com',
6037      height: el.getAttribute('data-height') || '360',
6038      width: el.getAttribute('data-width') || '640',
6039      videoId: el.getAttribute('data-id'),
6040      playerVars: {
6041        controls: el.getAttribute('data-controls') || 1,
6042        rel: 0,
6043        playsinline: 1
6044      }
6045    });
6046
6047    // Set player ID
6048    container.setAttribute('data-player', this.playerId);
6049
6050    // Register type
6051    container.setAttribute('data-type', 'youtube');
6052    container.classList.add('tobii-youtube');
6053    this.playerId++;
6054  }
6055  onPreload(container) {
6056    // Nothing
6057  }
6058  onLoad(container) {
6059    this.PLAYER[container.getAttribute('data-player')].playVideo();
6060  }
6061  onLeave(container) {
6062    if (this.PLAYER[container.getAttribute('data-player')].getPlayerState() === 1) {
6063      this.PLAYER[container.getAttribute('data-player')].pauseVideo();
6064    }
6065  }
6066  onCleanup(container) {
6067    if (this.PLAYER[container.getAttribute('data-player')].getPlayerState() === 1) {
6068      this.PLAYER[container.getAttribute('data-player')].pauseVideo();
6069    }
6070  }
6071  onReset() {
6072    // Nothing
6073  }
6074}
6075
6076function Tobii(userOptions) {
6077  /**
6078   * Global variables
6079   *
6080   */
6081  const SUPPORTED_ELEMENTS = {
6082    image: new ImageType(),
6083    // default
6084    html: new HtmlType(),
6085    iframe: new IframeType(),
6086    youtube: new YoutubeType()
6087  };
6088  const FOCUSABLE_ELEMENTS = ['a[href]:not([tabindex^="-"]):not([inert])', 'area[href]:not([tabindex^="-"]):not([inert])', 'input:not([disabled]):not([inert])', 'select:not([disabled]):not([inert])', 'textarea:not([disabled]):not([inert])', 'button:not([disabled]):not([inert])', 'iframe:not([tabindex^="-"]):not([inert])', 'audio:not([tabindex^="-"]):not([inert])', 'video:not([tabindex^="-"]):not([inert])', '[contenteditable]:not([tabindex^="-"]):not([inert])', '[tabindex]:not([tabindex^="-"]):not([inert])'];
6089  let userSettings = {};
6090  const WAITING_ELS = [];
6091  const GROUP_ATTS = {
6092    gallery: [],
6093    slider: null,
6094    sliderElements: [],
6095    elementsLength: 0,
6096    currentIndex: 0,
6097    x: 0
6098  };
6099  let lightbox = null;
6100  let prevButton = null;
6101  let nextButton = null;
6102  let closeButton = null;
6103  let counter = null;
6104  let lastFocus = null;
6105  let offset = null;
6106  let isYouTubeDependencyLoaded = false;
6107  let groups = {};
6108  let activeGroup = null;
6109  let pointerDownCache = [];
6110  let lastTapTime = 0;
6111  let liveRegion = null;
6112  const MIN_SCALE = 1;
6113  const MAX_SCALE = 4;
6114  const DOUBLE_TAP_TIME = 500; // milliseconds
6115  const SCALE_SENSITIVITY = 10;
6116  const TRANSFORM = {
6117    element: null,
6118    originX: 0,
6119    originY: 0,
6120    translateX: 0,
6121    translateY: 0,
6122    scale: MIN_SCALE
6123  };
6124  const DRAG = {
6125    startX: 0,
6126    startY: 0,
6127    x: 0,
6128    y: 0,
6129    distance: 0
6130  };
6131
6132  /**
6133   * Merge default options with user options
6134   *
6135   * @param {Object} userOptions - Optional user options
6136   * @returns {Object} - Custom options
6137   */
6138  const mergeOptions = userOptions => {
6139    // Default options
6140    const OPTIONS = {
6141      selector: '.lightbox',
6142      captions: true,
6143      captionsSelector: 'img',
6144      captionAttribute: 'alt',
6145      captionText: null,
6146      captionHTML: false,
6147      captionToggle: true,
6148      captionToggleLabel: ['Hide caption', 'Show caption'],
6149      nav: 'auto',
6150      navText: ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="15 6 9 12 15 18" /></svg>', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="9 6 15 12 9 18" /></svg>'],
6151      navLabel: ['Previous image', 'Next image'],
6152      announcementLabel: ['Slide', 'of'],
6153      close: true,
6154      closeText: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>',
6155      closeLabel: 'Close lightbox',
6156      dialogTitle: 'Lightbox',
6157      loadingIndicatorLabel: 'Image loading',
6158      counter: true,
6159      keyboard: true,
6160      zoom: false,
6161      zoomText: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="16 4 20 4 20 8" /><line x1="14" y1="10" x2="20" y2="4" /><polyline points="8 20 4 20 4 16" /><line x1="4" y1="20" x2="10" y2="14" /><polyline points="16 20 20 20 20 16" /><line x1="14" y1="14" x2="20" y2="20" /><polyline points="8 4 4 4 4 8" /><line x1="4" y1="4" x2="10" y2="10" /></svg>',
6162      docClose: true,
6163      swipeClose: true,
6164      hideScrollbar: true,
6165      draggable: true,
6166      threshold: 100,
6167      theme: 'tobii--theme-default'
6168    };
6169    return _extends({}, OPTIONS, userOptions);
6170  };
6171
6172  /**
6173   * Init
6174   *
6175   */
6176  const init = userOptions => {
6177    // Merge user options into defaults
6178    userSettings = mergeOptions(userOptions);
6179
6180    // Create the lightbox container
6181    lightbox = document.createElement('div');
6182    lightbox.setAttribute('role', 'dialog');
6183    lightbox.setAttribute('aria-hidden', 'true');
6184    lightbox.setAttribute('aria-modal', 'true');
6185    lightbox.setAttribute('aria-label', userSettings.dialogTitle);
6186    lightbox.classList.add('tobii');
6187
6188    // Add theme class
6189    lightbox.classList.add(userSettings.theme);
6190
6191    // Create the previous button
6192    prevButton = document.createElement('button');
6193    prevButton.className = 'tobii__btn tobii__btn--previous';
6194    prevButton.setAttribute('type', 'button');
6195    prevButton.setAttribute('aria-label', userSettings.navLabel[0]);
6196    prevButton.innerHTML = userSettings.navText[0];
6197    lightbox.appendChild(prevButton);
6198
6199    // Create the next button
6200    nextButton = document.createElement('button');
6201    nextButton.className = 'tobii__btn tobii__btn--next';
6202    nextButton.setAttribute('type', 'button');
6203    nextButton.setAttribute('aria-label', userSettings.navLabel[1]);
6204    nextButton.innerHTML = userSettings.navText[1];
6205    lightbox.appendChild(nextButton);
6206
6207    // Create the close button
6208    closeButton = document.createElement('button');
6209    closeButton.className = 'tobii__btn tobii__btn--close';
6210    closeButton.setAttribute('type', 'button');
6211    closeButton.setAttribute('aria-label', userSettings.closeLabel);
6212    closeButton.innerHTML = userSettings.closeText;
6213    lightbox.appendChild(closeButton);
6214
6215    // Create the counter
6216    counter = document.createElement('div');
6217    counter.className = 'tobii__counter';
6218    lightbox.appendChild(counter);
6219
6220    // Create the live region
6221    liveRegion = document.createElement('div');
6222    liveRegion.className = 'tobii__sr';
6223    liveRegion.setAttribute('aria-live', 'polite');
6224    liveRegion.setAttribute('aria-atomic', 'true');
6225    lightbox.appendChild(liveRegion);
6226
6227    // Append to body
6228    document.body.appendChild(lightbox);
6229
6230    // Init only
6231    if (!userSettings.selector) return;
6232
6233    // Get a list of all elements within the document
6234    const LIGHTBOX_TRIGGER_ELS = document.querySelectorAll(userSettings.selector);
6235    if (!LIGHTBOX_TRIGGER_ELS) {
6236      throw new Error(`Ups, I can't find the selector ${userSettings.selector} on this website.`);
6237    }
6238    LIGHTBOX_TRIGGER_ELS.forEach(el => checkDependencies(el));
6239  };
6240
6241  /**
6242   * Check dependencies
6243   *
6244   * @param {HTMLElement} el - Element to add
6245   */
6246  const checkDependencies = el => {
6247    // Check if there is a YouTube video and if the YouTube iframe-API is ready
6248    if (document.querySelector('[data-type="youtube"]') !== null && !isYouTubeDependencyLoaded) {
6249      if (document.getElementById('iframe_api') === null) {
6250        const TAG = document.createElement('script');
6251        const FIRST_SCRIPT_TAG = document.getElementsByTagName('script')[0];
6252        TAG.id = 'iframe_api';
6253        TAG.src = 'https://www.youtube.com/iframe_api';
6254        FIRST_SCRIPT_TAG.parentNode.insertBefore(TAG, FIRST_SCRIPT_TAG);
6255      }
6256      if (WAITING_ELS.indexOf(el) === -1) {
6257        WAITING_ELS.push(el);
6258      }
6259      window.onYouTubePlayerAPIReady = () => {
6260        WAITING_ELS.forEach(waitingEl => {
6261          add(waitingEl);
6262        });
6263        isYouTubeDependencyLoaded = true;
6264      };
6265    } else {
6266      add(el);
6267    }
6268  };
6269
6270  /**
6271   * Get group name from element
6272   *
6273   * @param {HTMLElement} el
6274   * @return {string}
6275   */
6276  const getGroupName = el => {
6277    return el.hasAttribute('data-group') ? el.getAttribute('data-group') : 'default';
6278  };
6279
6280  /**
6281   * Copy an object. (The secure way)
6282   *
6283   * @param {object} object
6284   * @return {object}
6285   */
6286  const copyObject = object => {
6287    return JSON.parse(JSON.stringify(object));
6288  };
6289
6290  /**
6291   * Add element
6292   *
6293   * @param {HTMLElement} el - Element to add
6294   */
6295  const add = el => {
6296    const newGroup = getGroupName(el);
6297    if (!Object.prototype.hasOwnProperty.call(groups, newGroup)) {
6298      groups[newGroup] = copyObject(GROUP_ATTS);
6299
6300      // Create slider
6301      groups[newGroup].slider = document.createElement('div');
6302      groups[newGroup].slider.className = 'tobii__slider';
6303
6304      // Hide slider
6305      groups[newGroup].slider.setAttribute('aria-hidden', 'true');
6306      lightbox.appendChild(groups[newGroup].slider);
6307    }
6308
6309    // Check if element already exists
6310    if (groups[newGroup].gallery.indexOf(el) === -1) {
6311      groups[newGroup].gallery.push(el);
6312      groups[newGroup].elementsLength++;
6313
6314      // Set zoom icon if necessary
6315      if (userSettings.zoom && el.querySelector('img') && el.getAttribute('data-zoom') !== 'false' || el.getAttribute('data-zoom') === 'true') {
6316        const TOBII_ZOOM = document.createElement('div');
6317        TOBII_ZOOM.className = 'tobii-zoom__icon';
6318        TOBII_ZOOM.innerHTML = userSettings.zoomText;
6319        el.classList.add('tobii-zoom');
6320        el.appendChild(TOBII_ZOOM);
6321      }
6322
6323      // Bind click event handler
6324      el.addEventListener('click', triggerTobii);
6325
6326      // Create slide
6327      const SLIDE_ELEMENT = document.createElement('div');
6328      const SLIDE_ELEMENT_CONTENT = document.createElement('div');
6329      SLIDE_ELEMENT.className = 'tobii__slide';
6330      SLIDE_ELEMENT.style.position = 'absolute';
6331      SLIDE_ELEMENT.style.left = `${groups[newGroup].x * 100}%`;
6332
6333      // Hide slide
6334      SLIDE_ELEMENT.setAttribute('aria-hidden', 'true');
6335
6336      // Create type elements
6337      const model = getModel(el);
6338      model.init(el, SLIDE_ELEMENT_CONTENT, userSettings);
6339
6340      // Add slide content container to slide element
6341      SLIDE_ELEMENT.appendChild(SLIDE_ELEMENT_CONTENT);
6342
6343      // Add slide element to slider
6344      groups[newGroup].slider.appendChild(SLIDE_ELEMENT);
6345      groups[newGroup].sliderElements.push(SLIDE_ELEMENT);
6346      ++groups[newGroup].x;
6347      if (isOpen() && newGroup === activeGroup) {
6348        updateConfig();
6349        updateLightbox();
6350      }
6351    } else {
6352      throw new Error('Ups, element already added.');
6353    }
6354  };
6355
6356  /**
6357   * Remove element
6358   *
6359   * @param {HTMLElement} el - Element to remove
6360   */
6361  const remove = el => {
6362    const GROUP_NAME = getGroupName(el);
6363
6364    // Check if element exists
6365    const galleryIndex = groups[GROUP_NAME].gallery.indexOf(el);
6366    if (galleryIndex === -1) {
6367      throw new Error(`Ups, I can't find a slide for the element ${el}.`);
6368    }
6369    const SLIDE_ELEMENT = groups[GROUP_NAME].sliderElements[galleryIndex];
6370
6371    // If the element to be removed is the currently visible slide
6372    if (isOpen() && GROUP_NAME === activeGroup && galleryIndex === groups[GROUP_NAME].currentIndex) {
6373      if (groups[GROUP_NAME].elementsLength === 1) {
6374        close();
6375        throw new Error('Ups, I\'ve closed. There are no slides more to show.');
6376      } else {
6377        // Navigate away before removal
6378        if (groups[GROUP_NAME].currentIndex === 0) {
6379          next();
6380        } else {
6381          previous();
6382        }
6383        updateConfig();
6384        updateLightbox();
6385      }
6386    }
6387    groups[GROUP_NAME].gallery.splice(galleryIndex, 1);
6388    groups[GROUP_NAME].sliderElements.splice(galleryIndex, 1);
6389    groups[GROUP_NAME].elementsLength--;
6390    --groups[GROUP_NAME].x;
6391
6392    // Remove zoom icon if necessary
6393    if (userSettings.zoom && el.querySelector('.tobii-zoom__icon')) {
6394      const ZOOM_ICON = el.querySelector('.tobii-zoom__icon');
6395      ZOOM_ICON.parentNode.classList.remove('tobii-zoom');
6396      ZOOM_ICON.parentNode.removeChild(ZOOM_ICON);
6397    }
6398
6399    // Unbind click event handler
6400    el.removeEventListener('click', triggerTobii);
6401
6402    // Remove slide
6403    SLIDE_ELEMENT.parentNode.removeChild(SLIDE_ELEMENT);
6404  };
6405  const getModel = el => {
6406    const type = el.getAttribute('data-type');
6407    if (SUPPORTED_ELEMENTS[type] !== undefined) {
6408      return SUPPORTED_ELEMENTS[type];
6409    } else {
6410      // unknown - use default
6411      if (el.hasAttribute('data-type')) {
6412        console.log('Unknown lightbox element type: ' + type);
6413      }
6414      return SUPPORTED_ELEMENTS.image;
6415    }
6416  };
6417
6418  /**
6419   * Open Tobii
6420   *
6421   * @param {number} index - Index to load
6422   */
6423  const open = (index = 0) => {
6424    if (isOpen()) {
6425      throw new Error('Ups, I\'m aleady open.');
6426    }
6427    if (index === -1 || index >= groups[activeGroup].elementsLength) {
6428      throw new Error(`Ups, I can't find slide ${index}.`);
6429    }
6430    document.documentElement.classList.add('tobii-is-open');
6431    document.body.classList.add('tobii-is-open');
6432    document.body.classList.add('tobii-is-open-' + activeGroup);
6433    updateConfig();
6434
6435    // Hide close if necessary
6436    if (!userSettings.close) {
6437      closeButton.disabled = false;
vendor: 12,804 bytes, lines 6438-6884
6438      closeButton.setAttribute('aria-hidden', 'true');
6439    }
6440
6441    // Save user’s focus
6442    lastFocus = document.activeElement;
6443
6444    // Use `history.pushState()` to make sure the "Back" button behavior
6445    // that aligns with the user's expectations
6446    const stateObj = {
6447      tobii: 'close'
6448    };
6449    const url = window.location.href;
6450    window.history.pushState(stateObj, 'Image', url);
6451
6452    // Set current index
6453    groups[activeGroup].currentIndex = index;
6454    bindEvents();
6455
6456    // Load slide
6457    load(groups[activeGroup].currentIndex);
6458
6459    // Show slider
6460    groups[activeGroup].slider.setAttribute('aria-hidden', 'false');
6461
6462    // Show lightbox
6463    lightbox.setAttribute('aria-hidden', 'false');
6464    updateLightbox();
6465
6466    // Preload previous and next slide
6467    preload(groups[activeGroup].currentIndex + 1);
6468    preload(groups[activeGroup].currentIndex - 1);
6469    groups[activeGroup].slider.classList.add('tobii__slider--animate');
6470
6471    // Create and dispatch a new event
6472    const openEvent = new window.CustomEvent('open', {
6473      detail: {
6474        group: activeGroup
6475      }
6476    });
6477    lightbox.dispatchEvent(openEvent);
6478  };
6479
6480  /**
6481   * Close Tobii
6482   *
6483   */
6484  const close = () => {
6485    if (!isOpen()) {
6486      throw new Error('Ups, I\'m already closed.');
6487    }
6488    document.documentElement.classList.remove('tobii-is-open');
6489    document.body.classList.remove('tobii-is-open');
6490    document.body.classList.remove('tobii-is-open-' + activeGroup);
6491    unbindEvents();
6492
6493    // Remove entry in browser history
6494    if (window.history.state !== null) {
6495      if (window.history.state.tobii === 'close') {
6496        window.history.back();
6497      }
6498    }
6499
6500    // Reenable the user’s focus
6501    lastFocus.focus();
6502
6503    // Don't forget to cleanup our current element
6504    leave(groups[activeGroup].currentIndex);
6505    cleanup(groups[activeGroup].currentIndex);
6506
6507    // Hide lightbox
6508    lightbox.setAttribute('aria-hidden', 'true');
6509
6510    // Hide slider
6511    groups[activeGroup].slider.setAttribute('aria-hidden', 'true');
6512
6513    // Reset current index
6514    groups[activeGroup].currentIndex = 0;
6515
6516    // Remove the hack to prevent animation during opening
6517    groups[activeGroup].slider.classList.remove('tobii__slider--animate');
6518
6519    // Create and dispatch a new event
6520    const closeEvent = new window.CustomEvent('close', {
6521      detail: {
6522        group: activeGroup
6523      }
6524    });
6525    lightbox.dispatchEvent(closeEvent);
6526  };
6527
6528  /**
6529   * Preload slide
6530   *
6531   * @param {number} index - Index to preload
6532   */
6533  const preload = index => {
6534    if (groups[activeGroup].sliderElements[index] === undefined) {
6535      return;
6536    }
6537    const CONTAINER = groups[activeGroup].sliderElements[index].querySelector('[data-type]');
6538    const model = getModel(CONTAINER);
6539    model.onPreload(CONTAINER);
6540  };
6541
6542  /**
6543   * Load slide
6544   * Will be called when opening the lightbox or moving index
6545   *
6546   * @param {number} index - Index to load
6547   */
6548  const load = index => {
6549    if (groups[activeGroup].sliderElements[index] === undefined) {
6550      return;
6551    }
6552    const CONTAINER = groups[activeGroup].sliderElements[index].querySelector('[data-type]');
6553    const model = getModel(CONTAINER);
6554    model.onLoad(CONTAINER, activeGroup);
6555  };
6556
6557  /**
6558   * Select a slide
6559   *
6560   * @param {number} index - Index to select
6561   */
6562  const select = index => {
6563    const currIndex = groups[activeGroup].currentIndex;
6564    if (!isOpen()) {
6565      throw new Error('Ups, I\'m closed.');
6566    }
6567    if (isOpen()) {
6568      if (!index && index !== 0) {
6569        throw new Error('Ups, no slide specified.');
6570      }
6571      if (index === groups[activeGroup].currentIndex) {
6572        throw new Error(`Ups, slide ${index} is already selected.`);
6573      }
6574      if (index === -1 || index >= groups[activeGroup].elementsLength) {
6575        throw new Error(`Ups, I can't find slide ${index}.`);
6576      }
6577    }
6578
6579    // Set current index
6580    groups[activeGroup].currentIndex = index;
6581    leave(currIndex);
6582    load(index);
6583    if (index < currIndex) {
6584      updateLightbox('left');
6585      cleanup(currIndex);
6586      preload(index - 1);
6587    }
6588    if (index > currIndex) {
6589      updateLightbox('right');
6590      cleanup(currIndex);
6591      preload(index + 1);
6592    }
6593  };
6594
6595  /**
6596   * Select the previous slide
6597   *
6598   */
6599  const previous = () => {
6600    if (!isOpen()) {
6601      throw new Error('Ups, I\'m closed.');
6602    }
6603    if (groups[activeGroup].currentIndex > 0) {
6604      leave(groups[activeGroup].currentIndex);
6605      load(--groups[activeGroup].currentIndex);
6606      updateLightbox('left');
6607      cleanup(groups[activeGroup].currentIndex + 1);
6608      preload(groups[activeGroup].currentIndex - 1);
6609    }
6610
6611    // Create and dispatch a new event
6612    const previousEvent = new window.CustomEvent('previous', {
6613      detail: {
6614        group: activeGroup
6615      }
6616    });
6617    lightbox.dispatchEvent(previousEvent);
6618  };
6619
6620  /**
6621   * Select the next slide
6622   *
6623   */
6624  const next = () => {
6625    if (!isOpen()) {
6626      throw new Error('Ups, I\'m closed.');
6627    }
6628    if (groups[activeGroup].currentIndex < groups[activeGroup].elementsLength - 1) {
6629      leave(groups[activeGroup].currentIndex);
6630      load(++groups[activeGroup].currentIndex);
6631      updateLightbox('right');
6632      cleanup(groups[activeGroup].currentIndex - 1);
6633      preload(groups[activeGroup].currentIndex + 1);
6634    }
6635
6636    // Create and dispatch a new event
6637    const nextEvent = new window.CustomEvent('next', {
6638      detail: {
6639        group: activeGroup
6640      }
6641    });
6642    lightbox.dispatchEvent(nextEvent);
6643  };
6644
6645  /**
6646   * Select a group
6647   *
6648   * @param {string} name - Name of the group to select
6649   */
6650  const selectGroup = name => {
6651    if (isOpen()) {
6652      throw new Error('Ups, I\'m open.');
6653    }
6654    if (!name) {
6655      throw new Error('Ups, no group specified.');
6656    }
6657    if (name && !Object.prototype.hasOwnProperty.call(groups, name)) {
6658      throw new Error(`Ups, I don't have a group called "${name}".`);
6659    }
6660    activeGroup = name;
6661  };
6662
6663  /**
6664   * Leave slide
6665   * Will be called before moving index
6666   *
6667   * @param {number} index - Index to leave
6668   */
6669  const leave = index => {
6670    if (groups[activeGroup].sliderElements[index] === undefined) {
6671      return;
6672    }
6673    const CONTAINER = groups[activeGroup].sliderElements[index].querySelector('[data-type]');
6674    const model = getModel(CONTAINER);
6675    model.onLeave(CONTAINER);
6676  };
6677
6678  /**
6679   * Cleanup slide
6680   * Will be called after moving index
6681   *
6682   * @param {number} index - Index to cleanup
6683   */
6684  const cleanup = index => {
6685    if (groups[activeGroup].sliderElements[index] === undefined) {
6686      return;
6687    }
6688    const CONTAINER = groups[activeGroup].sliderElements[index].querySelector('[data-type]');
6689    const model = getModel(CONTAINER);
6690    model.onCleanup(CONTAINER);
6691    DRAG.startX = 0;
6692    DRAG.startY = 0;
6693    DRAG.x = 0;
6694    DRAG.y = 0;
6695    DRAG.distance = 0;
6696    lastTapTime = 0;
6697    if (isZoomed()) resetZoom();
6698    TRANSFORM.element = null;
6699  };
6700
6701  /**
6702   * Update offset
6703   *
6704   */
6705  const updateOffset = () => {
6706    offset = -groups[activeGroup].currentIndex * lightbox.offsetWidth;
6707    groups[activeGroup].slider.style.transform = `translate(${offset}px, 0)`;
6708  };
6709
6710  /**
6711   * Update counter
6712   *
6713   */
6714  const updateCounter = () => {
6715    counter.innerHTML = `<p>${groups[activeGroup].currentIndex + 1}/${groups[activeGroup].elementsLength}</p>`;
6716  };
6717
6718  /**
6719   * Update focus
6720   *
6721   * @param {string|null} dir - Current slide direction
6722   */
6723  const updateFocus = dir => {
6724    const group = groups[activeGroup];
6725    const isNavEnabled = userSettings.nav === true || userSettings.nav === 'auto';
6726    const hasMultipleSlides = group.elementsLength > 1;
6727    if (isNavEnabled && !isTouchDevice() && hasMultipleSlides) {
6728      setButtonState(prevButton, true, true);
6729      setButtonState(nextButton, true, true);
6730      if (group.currentIndex === 0) {
6731        setButtonState(nextButton, false, false);
6732        nextButton.focus();
6733      } else if (group.currentIndex === group.elementsLength - 1) {
6734        setButtonState(prevButton, false, false);
6735        prevButton.focus();
6736      } else {
6737        setButtonState(prevButton, false, false);
6738        setButtonState(nextButton, false, false);
6739        if (dir === 'left') {
6740          prevButton.focus();
6741        } else {
6742          nextButton.focus();
6743        }
6744      }
6745    } else if (userSettings.close) {
6746      closeButton.focus();
6747    }
6748  };
6749
6750  /**
6751   * Resize event
6752   *
6753   */
6754  const resizeHandler = () => {
6755    updateOffset();
6756  };
6757
6758  /**
6759   * Click event handler to trigger Tobii
6760   *
6761   */
6762  const triggerTobii = event => {
6763    event.preventDefault();
6764    activeGroup = getGroupName(event.currentTarget);
6765    open(groups[activeGroup].gallery.indexOf(event.currentTarget));
6766  };
6767
6768  /**
6769   * Click event handler
6770   *
6771   */
6772  const clickHandler = event => {
6773    if (event.target === prevButton) {
6774      previous();
6775    } else if (event.target === nextButton) {
6776      next();
6777    } else if (event.target === closeButton || event.target.classList.contains('tobii__slide') || event.target.classList.contains('tobii') && userSettings.docClose) {
6778      close();
6779    }
6780    event.stopPropagation();
6781  };
6782
6783  /**
6784   * Set the hidden/disabled state of a button
6785   *
6786   */
6787  const setButtonState = (button, hidden, disabled) => {
6788    button.setAttribute('aria-hidden', hidden ? 'true' : 'false');
6789    button.disabled = disabled;
6790  };
6791
6792  /**
6793   * Keydown event handler
6794   *
6795   */
6796  const keydownHandler = event => {
6797    if (event.code === 'Tab') {
6798      const FOCUSABLE = Array.from(lightbox.querySelectorAll(FOCUSABLE_ELEMENTS.join(', ')));
6799      if (FOCUSABLE.length === 0) return;
6800      const FOCUSED_INDEX = FOCUSABLE.findIndex(el => el === document.activeElement);
6801      if (event.shiftKey && FOCUSED_INDEX === 0) {
6802        // SHIFT+Tab on first → jump to last
6803        FOCUSABLE[FOCUSABLE.length - 1].focus();
6804        event.preventDefault();
6805      } else if (!event.shiftKey && FOCUSED_INDEX === FOCUSABLE.length - 1) {
6806        // Tab on last → jump to first
6807        FOCUSABLE[0].focus();
6808        event.preventDefault();
6809      }
6810    } else if (event.code === 'Escape') {
6811      // `ESC` Key: Close Tobii
6812      event.preventDefault();
6813      close();
6814    } else if (event.code === 'ArrowLeft') {
6815      // `PREV` Key: Show the previous slide
6816      event.preventDefault();
6817      previous();
6818    } else if (event.code === 'ArrowRight') {
6819      // `NEXT` Key: Show the next slide
6820      event.preventDefault();
6821      next();
6822    }
6823  };
6824
6825  /**
6826   * Contextmenu event handler
6827   * This is a fix for chromium based browser on mac.
6828   * The 'contextmenu' terminates a mouse event sequence.
6829   * https://bugs.chromium.org/p/chromium/issues/detail?id=506801
6830   *
6831   */
6832  const contextmenuHandler = () => {
6833    pointerDownCache = [];
6834    updateOffset();
6835    groups[activeGroup].slider.classList.remove('tobii__slider--is-' + (isZoomed() ? 'moving' : 'dragging'));
6836  };
6837
6838  /**
6839   * Pointerdown event handler
6840   *
6841   */
6842  const pointerdownHandler = event => {
6843    // Prevent dragging / swiping on textareas, inputs and selects
6844    if (isIgnoreElement(event.target)) {
6845      return;
6846    }
6847    event.preventDefault();
6848    event.stopPropagation();
6849    DRAG.startX = DRAG.x = event.clientX;
6850    DRAG.startY = DRAG.y = event.clientY;
6851    DRAG.distance = 0;
6852
6853    // This event is cached to support 2-finger gestures
6854    pointerDownCache.push(event);
6855    if (pointerDownCache.length === 2) {
6856      const {
6857        x,
6858        y
6859      } = midPoint(pointerDownCache[0].clientX, pointerDownCache[0].clientY, pointerDownCache[1].clientX, pointerDownCache[1].clientY);
6860      DRAG.startX = DRAG.x = x;
6861      DRAG.startY = DRAG.y = y;
6862      DRAG.distance = distance(pointerDownCache[0].clientX - pointerDownCache[1].clientX, pointerDownCache[0].clientY - pointerDownCache[1].clientY) / TRANSFORM.scale;
6863    }
6864  };
6865
6866  /**
6867   * Pointermove event handler
6868   *
6869   */
6870  const pointermoveHandler = event => {
6871    if (!pointerDownCache.length) return;
6872    groups[activeGroup].slider.classList.add('tobii__slider--is-' + (isZoomed() ? 'moving' : 'dragging'));
6873
6874    // Find this event in the cache and update its record with this event
6875    const index = pointerDownCache.findIndex(cachedEv => cachedEv.pointerId === event.pointerId);
6876    pointerDownCache[index] = event;
6877    if (pointerDownCache.length === 2) {
6878      // 2-pointer horizontal pinch/zoom gesture
6879      const {
6880        x,
6881        y
6882      } = midPoint(pointerDownCache[0].clientX, pointerDownCache[0].clientY, pointerDownCache[1].clientX, pointerDownCache[1].clientY);
6883      const scale = distance(pointerDownCache[0].clientX - pointerDownCache[1].clientX, pointerDownCache[0].clientY - pointerDownCache[1].clientY) / DRAG.distance;
6884      zoomPan(event.target, clamp(scale, MIN_SCALE, MAX_SCALE), x, y, x - DRAG.x, y - DRAG.
vendor: 11,275 bytes, lines 6884-7292
6884y);
6885      DRAG.x = x;
6886      DRAG.y = y;
6887      return;
6888    }
6889    if (isZoomed()) {
6890      const deltaX = event.clientX - DRAG.x;
6891      const deltaY = event.clientY - DRAG.y;
6892      pan(deltaX, deltaY);
6893    }
6894    DRAG.x = event.clientX;
6895    DRAG.y = event.clientY;
6896    if (!isZoomed()) {
6897      // Drag animation
6898      const deltaX = DRAG.startX - DRAG.x;
6899      const deltaY = DRAG.startY - DRAG.y;
6900
6901      // Skip animation if drag distance is too low
6902      if (distance(deltaX, deltaY) < 10) return;
6903      if (Math.abs(deltaX) > Math.abs(deltaY) && groups[activeGroup].elementsLength > 1) {
6904        // Horizontal swipe
6905        groups[activeGroup].slider.style.transform = `translate(${offset - Math.round(deltaX)}px, 0)`;
6906      } else if (userSettings.swipeClose) {
6907        // Vertical swipe
6908        groups[activeGroup].slider.style.transform = `translate(${offset}px, -${Math.round(deltaY)}px)`;
6909      }
6910    }
6911  };
6912
6913  /**
6914   * Pointerup event handler
6915   *
6916   */
6917  const pointerupHandler = event => {
6918    // Intercept regular click handler
6919    if (!pointerDownCache.length) return;
6920    groups[activeGroup].slider.classList.remove('tobii__slider--is-' + (isZoomed() ? 'moving' : 'dragging'));
6921
6922    // Remove this event from the target's cache
6923    const index = pointerDownCache.findIndex(cachedEv => cachedEv.pointerId === event.pointerId);
6924    pointerDownCache.splice(index, 1);
6925    const x = event.clientX;
6926    const y = event.clientY;
6927    const deltaX = DRAG.startX - x;
6928    const deltaY = DRAG.startY - y;
6929    const distanceX = Math.abs(deltaX);
6930    const distanceY = Math.abs(deltaY);
6931    if (distanceX > 8 || distanceY > 8) {
6932      if (!isZoomed()) {
6933        // Evaluate drag
6934        if (deltaX < 0 && distanceX > userSettings.threshold && groups[activeGroup].currentIndex > 0) {
6935          previous();
6936        } else if (deltaX > 0 && distanceX > userSettings.threshold && groups[activeGroup].currentIndex !== groups[activeGroup].elementsLength - 1) {
6937          next();
6938        } else if (deltaY > 0 && distanceY > userSettings.threshold && userSettings.swipeClose) {
6939          close();
6940        } else {
6941          updateOffset();
6942        }
6943      }
6944    } else {
6945      // Evaluate tap
6946      const now = Date.now();
6947      const tapLength = now - lastTapTime;
6948      if (tapLength < DOUBLE_TAP_TIME && tapLength > 100) {
6949        // Double click
6950        event.preventDefault();
6951        lastTapTime = 0;
6952        if (isZoomed()) {
6953          resetZoom();
6954        } else {
6955          zoomPan(event.target, MAX_SCALE / 2, x, y, 0, 0);
6956        }
6957      } else {
6958        lastTapTime = now;
6959        if (isTouchDevice()) {
6960          // Delayed tap on mobile
6961          window.setTimeout(() => {
6962            const {
6963              left,
6964              top,
6965              bottom,
6966              right,
6967              width
6968            } = event.target.getBoundingClientRect();
6969            if (y < top || y > bottom || !lastTapTime) return;
6970            if (x > left && x < left + width / 2) {
6971              previous();
6972            } else if (x < right && x > right - width / 2) {
6973              next();
6974            }
6975          }, DOUBLE_TAP_TIME);
6976        }
6977      }
6978    }
6979  };
6980
6981  /**
6982   * Wheel event handler
6983   *
6984   */
6985  const wheelHandler = event => {
6986    const deltaScale = Math.sign(event.deltaY) > 0 ? -1 : 1;
6987    if (!isZoomed() && !deltaScale) return;
6988    event.preventDefault();
6989    const newScale = TRANSFORM.scale + deltaScale / (SCALE_SENSITIVITY / TRANSFORM.scale);
6990    zoomPan(event.target, clamp(newScale, MIN_SCALE, MAX_SCALE), event.clientX, event.clientY, 0, 0);
6991  };
6992  const clampedTranslate = (axis, translate) => {
6993    // Whole clamping functionality heavily inspired
6994    // by https://github.com/Neophen/pinch-zoom-pan
6995    const {
6996      element,
6997      scale,
6998      originX,
6999      originY
7000    } = TRANSFORM;
7001    const axisIsX = axis === 'x';
7002    const origin = axisIsX ? originX : originY;
7003    const axisKey = axisIsX ? 'offsetWidth' : 'offsetHeight';
7004    const containerSize = element.parentNode[axisKey];
7005    const imageSize = element[axisKey];
7006    const bounds = element.getBoundingClientRect();
7007    const imageScaledSize = axisIsX ? bounds.width : bounds.height;
7008    const defaultOrigin = imageSize / 2;
7009    const originOffset = (origin - defaultOrigin) * (scale - 1);
7010    const range = Math.max(0, Math.round(imageScaledSize) - containerSize);
7011    const max = Math.round(range / 2);
7012    const min = 0 - max;
7013    return clamp(translate, min + originOffset, max + originOffset);
7014  };
7015  const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
7016  const isZoomed = () => TRANSFORM.scale !== MIN_SCALE;
7017  const pan = (deltaX, deltaY) => {
7018    if (deltaX !== 0) {
7019      TRANSFORM.translateX = clampedTranslate('x', TRANSFORM.translateX + deltaX);
7020    }
7021    if (deltaY !== 0) {
7022      TRANSFORM.translateY = clampedTranslate('y', TRANSFORM.translateY + deltaY);
7023    }
7024    const {
7025      element,
7026      originX,
7027      originY,
7028      translateX,
7029      translateY,
7030      scale
7031    } = TRANSFORM;
7032    element.style.transformOrigin = `${originX}px ${originY}px`;
7033    element.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
7034  };
7035  const zoomPan = (el, newScale, x, y, deltaX, deltaY) => {
7036    if (el.tagName !== 'IMG') return;
7037    const {
7038      left,
7039      top
7040    } = el.getBoundingClientRect();
7041    const originX = x - left;
7042    const originY = y - top;
7043    const newOriginX = originX / TRANSFORM.scale;
7044    const newOriginY = originY / TRANSFORM.scale;
7045    TRANSFORM.element = el;
7046    TRANSFORM.originX = newOriginX;
7047    TRANSFORM.originY = newOriginY;
7048    TRANSFORM.scale = newScale;
7049    pan(deltaX, deltaY);
7050  };
7051  const distance = (dx, dy) => Math.hypot(dx, dy);
7052  const midPoint = (x1, y1, x2, y2) => ({
7053    x: (x1 + x2) / 2,
7054    y: (y1 + y2) / 2
7055  });
7056  const resetZoom = () => {
7057    TRANSFORM.scale = MIN_SCALE;
7058    TRANSFORM.originX = 0;
7059    TRANSFORM.originY = 0;
7060    TRANSFORM.translateX = 0;
7061    TRANSFORM.translateY = 0;
7062    pan(0, 0);
7063  };
7064
7065  /**
7066   * Bind events
7067   *
7068   */
7069  const bindEvents = () => {
7070    if (userSettings.keyboard) {
7071      window.addEventListener('keydown', keydownHandler);
7072    }
7073
7074    // Resize event
7075    window.addEventListener('resize', resizeHandler);
7076
7077    // Popstate event
7078    window.addEventListener('popstate', close);
7079
7080    // Click event
7081    on('click', clickHandler);
7082    if (userSettings.draggable) {
7083      // Pointer events
7084      on('pointerdown', pointerdownHandler);
7085      on('pointermove', pointermoveHandler);
7086      on('pointerup', pointerupHandler);
7087      on('pointercancel', contextmenuHandler);
7088      on('pointerout', contextmenuHandler);
7089      on('pointerleave', contextmenuHandler);
7090      on('contextmenu', contextmenuHandler);
7091    }
7092
7093    // Wheel event
7094    on('wheel', wheelHandler);
7095  };
7096
7097  /**
7098   * Unbind events
7099   *
7100   */
7101  const unbindEvents = () => {
7102    if (userSettings.keyboard) {
7103      window.removeEventListener('keydown', keydownHandler);
7104    }
7105
7106    // Resize event
7107    window.removeEventListener('resize', resizeHandler);
7108
7109    // Popstate event
7110    window.removeEventListener('popstate', close);
7111
7112    // Click event
7113    off('click', clickHandler);
7114    if (userSettings.draggable) {
7115      // Pointer events
7116      off('pointerdown', pointerdownHandler);
7117      off('pointermove', pointermoveHandler);
7118      off('pointerup', pointerupHandler);
7119      off('pointercancel', contextmenuHandler);
7120      off('pointerout', contextmenuHandler);
7121      off('pointerleave', contextmenuHandler);
7122      off('contextmenu', contextmenuHandler);
7123    }
7124
7125    // Wheel event
7126    off('wheel', wheelHandler);
7127  };
7128
7129  /**
7130   * Update userSettings
7131   *
7132   */
7133  const updateConfig = () => {
7134    const group = groups[activeGroup];
7135    const slider = group.slider;
7136    if (userSettings.draggable && !slider.classList.contains('tobii__slider--is-draggable')) {
7137      slider.classList.add('tobii__slider--is-draggable');
7138    }
7139    const hideButtons = !userSettings.nav || group.elementsLength === 1 || userSettings.nav === 'auto' && isTouchDevice();
7140    setButtonState(prevButton, hideButtons, hideButtons);
7141    setButtonState(nextButton, hideButtons, hideButtons);
7142    const hideCounter = !userSettings.counter || group.elementsLength === 1;
7143    counter.setAttribute('aria-hidden', hideCounter ? 'true' : 'false');
7144  };
7145
7146  /**
7147   * Update live region
7148   *
7149   */
7150  const updateAnnouncement = () => {
7151    const group = groups[activeGroup];
7152    const currIndex = group.currentIndex;
7153    const total = group.elementsLength;
7154    const trigger = group.gallery[currIndex];
7155    const [slide, of] = userSettings.announcementLabel;
7156    let extra;
7157    if (trigger.hasAttribute('data-label')) {
7158      extra = trigger.getAttribute('data-label');
7159    } else {
7160      const img = trigger.querySelector('img');
7161      extra = (img == null ? void 0 : img.alt) || '';
7162    }
7163    const base = `${slide} ${currIndex + 1} ${of} ${total}`;
7164
7165    // Announce reliably
7166    liveRegion.textContent = '';
7167    window.setTimeout(() => {
7168      liveRegion.textContent = extra ? `${base}. ${extra}` : base;
7169    }, 10);
7170  };
7171
7172  /**
7173   * Update lightbox
7174   *
7175   * @param {string|null} dir - Current slide direction
7176   */
7177  const updateLightbox = (dir = null) => {
7178    updateOffset();
7179    updateCounter();
7180    updateAnnouncement();
7181    updateFocus(dir);
7182  };
7183
7184  /**
7185   * Reset Tobii
7186   *
7187   */
7188  const reset = () => {
7189    if (isOpen()) close();
7190    Object.values(groups).forEach(group => group.gallery.forEach(remove));
7191    groups = {};
7192    activeGroup = null;
7193    Object.values(SUPPORTED_ELEMENTS).forEach(type => type.onReset());
7194  };
7195
7196  /**
7197   * Destroy Tobii
7198   *
7199   */
7200  const destroy = () => {
7201    reset();
7202    lightbox.parentNode.removeChild(lightbox);
7203  };
7204
7205  /**
7206   * Check if Tobii is open
7207   *
7208   */
7209  const isOpen = () => {
7210    return lightbox.getAttribute('aria-hidden') === 'false';
7211  };
7212
7213  /**
7214   * Detect whether device is touch capable
7215   *
7216   */
7217  const isTouchDevice = () => {
7218    return 'ontouchstart' in window;
7219  };
7220
7221  /**
7222   * Checks whether element's tagName is part of array
7223   *
7224   */
7225  const isIgnoreElement = el => {
7226    return ['TEXTAREA', 'OPTION', 'INPUT', 'SELECT'].indexOf(el.tagName) !== -1 || el === prevButton || el === nextButton || el === closeButton;
7227  };
7228
7229  /**
7230   * Return current index
7231   *
7232   */
7233  const slidesIndex = () => {
7234    return groups[activeGroup].currentIndex;
7235  };
7236
7237  /**
7238   * Return elements length
7239   *
7240   */
7241  const slidesCount = () => {
7242    return groups[activeGroup].elementsLength;
7243  };
7244
7245  /**
7246   * Return current group
7247   *
7248   */
7249  const currentGroup = () => {
7250    return activeGroup;
7251  };
7252
7253  /**
7254   * Bind events
7255   * @param {String} eventName
7256   * @param {function} callback - callback to call
7257   *
7258   */
7259  const on = (eventName, callback) => {
7260    lightbox.addEventListener(eventName, callback);
7261  };
7262
7263  /**
7264   * Unbind events
7265   * @param {String} eventName
7266   * @param {function} callback - callback to call
7267   *
7268   */
7269  const off = (eventName, callback) => {
7270    lightbox.removeEventListener(eventName, callback);
7271  };
7272  init(userOptions);
7273  return {
7274    open,
7275    previous,
7276    next,
7277    close,
7278    add: checkDependencies,
7279    remove,
7280    reset,
7281    destroy,
7282    isOpen,
7283    slidesIndex,
7284    select,
7285    slidesCount,
7286    selectGroup,
7287    currentGroup,
7288    on,
7289    off
7290  };
7291}
7292
7293class CsLightbox extends Core {
7294
7295  static options = {
7296    zoomImageSelector: '[data-type="image"] img, .js-zoom-image'
7297  };
7298
7299  applyTobiiWheelFix() {
7300    const minScale = 1;
7301    const maxScale = 4;
7302    const tolerance = 0.01;
7303
7304    const getScale = (image) => {
7305      const transform = getComputedStyle(image).transform;
7306
7307      if (!transform || transform === 'none') {
7308        return minScale;
7309      }
7310
7311      try {
7312        const matrix = new DOMMatrixReadOnly(transform);
7313
7314        return Math.hypot(matrix.a, matrix.b);
7315      } catch {
7316        return minScale;
7317      }
7318    };
7319
7320    this.lightbox.addEventListener(
7321      'wheel',
7322      (event) => {
7323        const target = event.target instanceof Element ? event.target : null;
7324
7325        const image = target?.closest('img');
7326
7327        const isZoomImage = image?.matches(
7328          CsLightbox.options.zoomImageSelector
7329        );
7330
7331        // Keep native scrolling for non-zoomable content.
7332        if (!isZoomImage || event.deltaY === 0) {
7333          event.stopImmediatePropagation();
7334          return;
7335        }
7336
7337        const scale = getScale(image);
7338        const zoomsIn = event.deltaY < 0;
7339        const zoomsOut = event.deltaY > 0;
7340
7341        const isAtMinimum = scale <= minScale + tolerance;
7342        const isAtMaximum = scale >= maxScale - tolerance;
7343
7344        // Keep native scrolling when the zoom limit is reached.
7345        if ((zoomsOut && isAtMinimum) || (zoomsIn && isAtMaximum)) {
7346          event.stopImmediatePropagation();
7347        }
7348      },
7349      {
7350        capture: true,
7351        passive: true
7352      }
7353    );
7354  }
7355
7356  setCloseButtonPosition(closeButton, innerPosition = false, activeSlideMedia = null, delay = 0) {
7357    if (delay > 0) {
7358      closeButton.classList.remove('active');
7359    }
7360
7361    if (!innerPosition) {
7362      closeButton.removeAttribute('style');
7363    } else {
7364      const containerRect = this.lightbox.getBoundingClientRect();
7365      const mediaRect = activeSlideMedia.getBoundingClientRect();
7366
7367      const buttonHalfWidth = closeButton.offsetWidth / 2;
7368      const buttonHalfHeight = closeButton.offsetHeight / 2;
7369
7370      closeButton.style.top = `${mediaRect.top - buttonHalfHeight}px`;
7371
7372      const spaceOnSide = (containerRect.width - mediaRect.width) / 2;
7373      closeButton.style.right = spaceOnSide < closeButton.offsetWidth ? '0' : `${spaceOnSide - buttonHalfWidth}px`;
7374    }
7375
7376    if (delay > 0) {
7377      setTimeout (() => {
7378        closeButton.classList.add('active');
7379      }, delay);
7380    } else {
7381      closeButton.classList.add('active');
7382    }
7383  }
7384
7385  checkCloseButtonPosition(delay = 0) {
7386    const closeButton = this.lightbox.querySelector('.tobii__btn--close');
7387    if (!(closeButton instanceof HTMLElement) || !this.tobii.isOpen()) {
7388      // consider resize event could be triggered if lightbox is closed
7389      return;
7390    }
7391
7392    // check if close button position is set to inner
7393    const currentGroup = this.tobii.currentGroup();
7394    const trigger = document.querySelector(`.js-lightbox[data-group="${currentGroup}"]`);
7395    const activeSlideContainer = this.lightbox.querySelector('.tobii__slider[aria-hidden="false"]');
7396    if (!(activeSlideContainer instanceof HTMLElement) || !trigger?.closest('.js-lightbox-close-button-inner')) {
7397      this.setCloseButtonPosition(closeButton);
7398      return;
7399    }
7400
7401    const slides = activeSlideContainer.querySelectorAll('.tobii__slide');
7402    const activeSlide = slides[this.tobii.slidesIndex()];
7403    const activeSlideMedia = activeSlide?.querySelector('.tobii-image, .tobii-html, [data-type="image"], [data-type="html"]');
7404    if (!(activeSlideMedia instanceof HTMLElement)) {
7405      this.setCloseButtonPosition(closeButton);
7406      return;
7407    }
7408
7409    this.setCloseButtonPosition(closeButton, true, activeSlideMedia, delay);
7410  }
7411
7412  handleCloseButtonPosition() {
7413    const delay = 250;
7414
7415    this.tobii.on('open', () => this.checkCloseButtonPosition());
7416    this.tobii.on('previous', () => this.checkCloseButtonPosition(delay));
7417    this.tobii.on('next', () => this.checkCloseButtonPosition(delay));
7418
7419    window.addEventListener('resize', () => this.checkCloseButtonPosition(delay));
7420  }
7421
7422  initLightbox() {
7423
7424    let langConfig = {};
7425
7426    try {
7427      langConfig = TranslatedLang.translatedLangConfig('lightbox').csConfig;
7428    } catch (error){
7429      console.error('Labels for lightbox not defined', error);
7430    }
7431
7432    // see https://github.com/midzer/tobii
7433    this.tobii = new Tobii({
7434      selector: '.js-lightbox',
7435      captionsSelector: 'self',
7436      captionAttribute: 'data-lightbox-caption',
7437      captionToggle: true,
7438      captionToggleLabel: [langConfig['captionToggleLabel.show'], langConfig['captionToggleLabel.hide']],
7439      navLabel: [langConfig['navLabel.prev'], langConfig['navLabel.next']],
7440      announcementLabel: [langConfig['announcementLabel.page'], langConfig['announcementLabel.of']],
7441      closeLabel: langConfig['closeLabel'],
7442      dialogTitle: langConfig['dialogTitle'],
7443      loadingIndicatorLabel: langConfig['loadingIndicatorLabel']
7444    });
7445
7446    this.lightbox = document.querySelector('.tobii:not(.js-wheel-fix-initialized)');
7447    if (!this.lightbox) {
7448      return;
7449    }
7450
7451    // fix for mouse wheel in tobii lightbox 3.2.0
7452    this.lightbox.classList.add('js-wheel-fix-initialized');
7453    this.applyTobiiWheelFix();
7454
7455    // set custom close button position, if option is set
7456    this.handleCloseButtonPosition();
7457  }
7458
7459  init() {
7460    this.initLightbox();
7461  }
7462}
7463
7464class CsScrollToNextFrame extends Core {
7465
7466  init() {
7467    this.el.addEventListener('click', (e) => {
7468      e.preventDefault();
7469
7470      const target = this.el.closest('.frame').nextElementSibling;
7471
7472      target.scrollIntoView({ behavior: 'smooth' });
7473    });
7474  }
7475}
7476
7477class DeviceDetection {
7478
7479  /**
7480   * Returns whether the current device is a touch device
7481   * @returns {boolean}
7482   */
7483  static isTouchDevice() {
7484    return ('ontouchstart' in document.documentElement);
7485  }
7486
7487  /**
7488   * Returns whether the current userAgent is an IOS device
7489   * @returns {boolean}
7490   */
7491  static isIOSDevice() {
7492    return (DeviceDetection.isIPhoneDevice() || DeviceDetection.isIPadDevice());
7493  }
7494
7495  /**
7496   * Returns if we're dealing with a native Windows browser
7497   * @returns {boolean}
7498   */
7499  static isNativeWindowsBrowser() {
7500    return (DeviceDetection.isIEBrowser() || DeviceDetection.isEdgeBrowser());
7501  }
7502
7503  /**
7504   * Returns whether the current userAgent is an iPhone device
7505   * @returns {boolean}
7506   */
7507  static isIPhoneDevice() {
7508    const userAgent = navigator.userAgent;
7509    return !!userAgent.match(/iPhone/i);
7510  }
7511
7512  /**
7513   * Returns whether the current userAgent is an iPad device
7514   * @returns {boolean}
7515   */
7516  static isIPadDevice() {
7517    const userAgent = navigator.userAgent;
7518    return !!userAgent.match(/iPad/i);
7519  }
7520
7521  /**
7522   * Returns if we're dealing with the Internet Explorer.
7523   * @returns {boolean}
7524   */
7525  static isIEBrowser() {
7526    const userAgent = navigator.userAgent.toLowerCase();
7527    return userAgent.indexOf('msie') !== -1 ||
7528      !!navigator.userAgent.match(/Trident.*rv:\d+\./);
7529  }
7530
7531  /**
7532   * Returns if we're dealing with the Windows Edge browser.
7533   * @returns {boolean}
7534   */
7535  static isEdgeBrowser() {
7536    const userAgent = navigator.userAgent;
7537    return !!userAgent.match(/Edge\/\d+/i);
7538  }
7539
7540  /**
7541   * Returns a list of css classes with the boolean result of all device
7542   * detection functions.
7543   * @returns {object}
7544   */
7545  static getList() {
7546    return {
7547      'is-touch': DeviceDetection.isTouchDevice(),
7548      'is-ios': DeviceDetection.isIOSDevice(),
7549      'is-native-windows': DeviceDetection.isNativeWindowsBrowser(),
7550      'is-iphone': DeviceDetection.isIPhoneDevice(),
7551      'is-ipad': DeviceDetection.isIPadDevice(),
7552      'is-ie': DeviceDetection.isIEBrowser(),
7553      'is-edge': DeviceDetection.isEdgeBrowser(),
7554    };
7555  }
7556}
7557
7558/*! js-cookie v3.0.5 | MIT */
7559/* eslint-disable no-var */
7560function assign (target) {
7561  for (var i = 1; i < arguments.length; i++) {
7562    var source = arguments[i];
7563    for (var key in source) {
7564      target[key] = source[key];
7565    }
7566  }
7567  return target
7568}
7569/* eslint-enable no-var */
7570
7571/* eslint-disable no-var */
7572var defaultConverter = {
7573  read: function (value) {
7574    if (value[0] === '"') {
7575      value = value.slice(1, -1);
7576    }
7577    return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
7578  },
7579  write: function (value) {
7580    return encodeURIComponent(value).replace(
7581      /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
7582      decodeURIComponent
7583    )
7584  }
7585};
7586/* eslint-enable no-var */
7587
7588/* eslint-disable no-var */
7589
7590function init (converter, defaultAttributes) {
7591  function set (name, value, attributes) {
7592    if (typeof document === 'undefined') {
7593      return
7594    }
7595
7596    attributes = assign({}, defaultAttributes, attributes);
7597
7598    if (typeof attributes.expires === 'number') {
7599      attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
7600    }
7601    if (attributes.expires) {
7602      attributes.expires = attributes.expires.toUTCString();
7603    }
7604
7605    name = encodeURIComponent(name)
7606      .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
7607      .replace(/[()]/g, escape);
7608
7609    var stringifiedAttributes = '';
7610    for (var attributeName in attributes) {
7611      if (!attributes[attributeName]) {
7612        continue
7613      }
7614
7615      stringifiedAttributes += '; ' + attributeName;
7616
7617      if (attributes[attributeName] === true) {
7618        continue
7619      }
7620
7621      // Considers RFC 6265 section 5.2:
7622      // ...
7623      // 3.  If the remaining unparsed-attributes contains a %x3B (";")
7624      //     character:
7625      // Consume the characters of the unparsed-attributes up to,
7626      // not including, the first %x3B (";") character.
7627      // ...
7628      stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
7629    }
7630
7631    return (document.cookie =
7632      name + '=' + converter.write(value, name) + stringifiedAttributes)
7633  }
7634
7635  function get (name) {
7636    if (typeof document === 'undefined' || (arguments.length && !name)) {
7637      return
7638    }
7639
7640    // To prevent the for loop in the first place assign an empty array
7641    // in case there are no cookies at all.
7642    var cookies = document.cookie ? document.cookie.split('; ') : [];
7643    var jar = {};
7644    for (var i = 0; i < cookies.length; i++) {
7645      var parts = cookies[i].split('=');
7646      var value = parts.slice(1).join('=');
7647
7648      try {
7649        var found = decodeURIComponent(parts[0]);
7650        jar[found] = converter.read(value, found);
7651
7652        if (name === found) {
7653          break
7654        }
7655      } catch (e) {}
7656    }
7657
7658    return name ? jar[name] : jar
7659  }
7660
7661  return Object.create(
7662    {
7663      set,
7664      get,
7665      remove: function (name, attributes) {
7666        set(
7667          name,
7668          '',
7669          assign({}, attributes, {
7670            expires: -1
7671          })
7672        );
7673      },
7674      withAttributes: function (attributes) {
7675        return init(this.converter, assign({}, this.attributes, attributes))
7676      },
7677      withConverter: function (converter) {
7678        return init(assign({}, this.converter, converter), this.attributes)
7679      }
7680    },
7681    {
7682      attributes: { value: Object.freeze(defaultAttributes) },
7683      converter: { value: Object.freeze(converter) }
7684    }
7685  )
7686}
7687
7688var api = init(defaultConverter, { path: '/' });
7689
7690class CsEdgeNav extends Core {
7691
7692  static options = {
7693    scrollCollapseAt: 400,
7694    itemSelector: '.js-edge-nav-item',
7695    linkSelector: '.js-edge-nav-link',
7696    closeSelector: '.js-edge-nav-close',
7697    smallClass: 'edge-nav__item--small',
7698    cookieDays: 7
7699  };
7700
7701  initItems() {
7702    const items = Array.from(this.el.querySelectorAll(this.options.itemSelector));
7703
7704    // Hide items with existing cookies
7705    items.forEach((item) => {
7706      const key = item.dataset.dismissKey;
7707      if (key && api.get(key) === '1') {
7708        this.hideItem(item);
7709      }
7710    });
7711
7712    // Hide region if no visible items
7713    if (!this.anyVisible()) {
7714      this.hideRegion();
7715      return;
7716    }
7717
7718    this.showRegion();
7719
7720    // Pointer + focus interactions
7721    items.forEach((item) => {
7722      item.addEventListener('mouseenter', () => {
7723        if (DeviceDetection.isTouchDevice()) {
7724          return;
7725        }
7726        this.expand(item);
7727      });
7728    });
7729
7730    this.el.addEventListener('focusin', (e) => {
7731      if (DeviceDetection.isTouchDevice()) {
7732        return;
7733      }
7734      const item = e.target.closest(this.options.itemSelector);
7735      if (item) this.expand(item);
7736    });
7737
7738    this.el.addEventListener('click', (e) => {
7739      // for touch devices
7740      const link = e.target.closest(this.options.linkSelector);
7741      if (link && this.el.contains(link)) {
7742        const item = link.closest(this.options.itemSelector);
7743        if (item && item.classList.contains(this.options.smallClass)) {
7744          e.preventDefault();
7745          this.expand(item);
7746        }
7747      }
7748
7749      // close button
7750      const close = e.target.closest(this.options.closeSelector);
7751      if (close && this.el.contains(close)) {
7752        e.preventDefault();
7753
7754        const item = close.closest(this.options.itemSelector);
7755        const key = item?.dataset.dismissKey;
7756
7757        this.fadeOut(item, 150).then(() => {
7758          if (key) api.set(key, '1', {expires: this.options.cookieDays, path: '/'});
7759          this.hideItem(item);
7760          if (!this.anyVisible()) this.hideRegion();
7761        });
7762      }
7763    });
7764
7765    // Scroll collapse
7766    window.addEventListener('scroll', () => {
7767      if (window.scrollY >= this.options.scrollCollapseAt) {
7768        this.collapseAll();
7769      }
7770    }, { passive: true });
7771  }
7772
7773  /* ------------------------------------------------------------
7774   * DOM helpers
7775   * ------------------------------------------------------------ */
7776
7777  hideItem(item) {
7778    item.hidden = true;
7779    item.setAttribute('aria-hidden', 'true');
7780    item.style.display = 'none';
7781  }
7782
7783  showRegion() {
7784    this.el.hidden = false;
7785    this.el.removeAttribute('aria-hidden');
7786  }
7787
7788  hideRegion() {
7789    this.el.hidden = true;
7790    this.el.setAttribute('aria-hidden', 'true');
7791  }
7792
7793  anyVisible() {
7794    return Array.from(this.el.querySelectorAll(this.options.itemSelector))
7795      .some(i => !i.hidden && i.style.display !== 'none');
7796  }
7797
7798  expand(item) {
7799    item.classList.remove(this.options.smallClass);
7800  }
7801
7802  collapse(item) {
7803    item.classList.add(this.options.smallClass);
7804  }
7805
7806  collapseAll() {
7807    this.el.querySelectorAll(this.options.itemSelector).forEach((i) => {
7808      if (!i.hidden) this.collapse(i);
7809    });
7810  }
7811
7812  fadeOut(el, duration = 150) {
7813    return new Promise((resolve) => {
7814      if (!el) return resolve();
7815      el.style.transition = `opacity ${duration}ms ease`;
7816      el.style.opacity = '1';
7817      requestAnimationFrame(() => { el.style.opacity = '0'; setTimeout(resolve, duration); });
7818    });
7819  }
7820
7821  init() {
7822    this.initItems();
7823  }
7824}
7825
7826class StringHelper {
7827
7828    /**
7829     * turns first character of word to uppercase
7830     *
7831     * @param {string} string
7832     * @returns {string}
7833     * @private
7834     */
7835    static ucFirst(string) {
7836        return string.charAt(0).toUpperCase() + string.slice(1);
7837    }
7838
7839
7840    /**
7841     * turns first character of string to lowercase
7842     *
7843     * @param {string} string
7844     * @returns {string}
7845     * @private
7846     */
7847    static lcFirst(string) {
7848        return string.charAt(0).toLowerCase() + string.slice(1);
7849    }
7850
7851    /**
7852     * converts a camel case string
7853     * into a dash case string
7854     *
7855     * @param string
7856     * @returns {string}
7857     */
7858    static toDashCase(string) {
7859        return string.replace(/([A-Z])/g, '-$1').replace(/^-/, '').toLowerCase();
7860    }
7861
7862    /**
7863     *
7864     * @param {string} string
7865     * @param {string} separator
7866     *
7867     * @returns {string}
7868     */
7869    static toLowerCamelCase(string, separator) {
7870        const upperCamelCase = StringHelper.toUpperCamelCase(string, separator);
7871        return StringHelper.lcFirst(upperCamelCase);
7872    }
7873
7874    /**
7875     *
7876     * @param {string} string
7877     * @param {string} separator
7878     *
7879     * @returns {string}
7880     */
7881    static toUpperCamelCase(string, separator) {
7882        if (!separator) {
7883            return StringHelper.ucFirst(string.toLowerCase());
7884        }
7885
7886        const stringParts = string.split(separator);
7887        return stringParts.map(string => StringHelper.ucFirst(string.toLowerCase())).join('');
7888    }
7889
7890    /**
7891     * returns primitive value of a string
7892     *
7893     * @param value
7894     * @returns {*}
7895     * @private
7896     */
7897    static parsePrimitive(value) {
7898        try {
7899            // replace comma with dot
7900            // if value only contains numbers and commas
7901            if (/^\d+(.|,)\d+$/.test(value)) {
7902                value = value.replace(',', '.');
7903            }
7904
7905            return JSON.parse(value);
7906        }
7907        catch {
7908            return value.toString();
7909        }
7910    }
7911}
7912
7913class DomAccess {
7914
7915  /**
7916   * Returns whether or not the element is an HTML node
7917   *
7918   * @param {HTMLElement} element
7919   * @returns {boolean}
7920   */
7921  static isNode(element) {
7922    if (typeof element !== 'object' || element === null) {
7923      return false;
7924    }
7925
7926    if (element === document || element === window) {
7927      return true;
7928    }
7929
7930    return element instanceof Node;
7931  }
7932
7933  /**
7934   * Returns if the given element has the requested attribute/property
7935   * @param {HTMLElement} element
7936   * @param {string} attribute
7937   * @returns {boolean}
7938   */
7939  static hasAttribute(element, attribute) {
7940    if (!DomAccess.isNode(element)) {
7941      throw new Error('The element must be a valid HTML Node!');
7942    }
7943
7944    if (typeof element.hasAttribute !== 'function') {
7945      return false;
7946    }
7947
7948    return element.hasAttribute(attribute);
7949  }
7950
7951  /**
7952   * Returns the value of a given element's attribute/property
7953   * @param {HTMLElement|EventTarget} element
7954   * @param {string} attribute
7955   * @param {boolean} strict
7956   * @returns {*|this|string}
7957   */
7958  static getAttribute(element, attribute, strict = true) {
7959    if (strict && DomAccess.hasAttribute(element, attribute) === false) {
7960      throw new Error(`The required property "${attribute}" does not exist!`);
7961    }
7962
7963    if (typeof element.getAttribute !== 'function') {
7964      if (strict) {
7965        throw new Error(
7966          'This node doesn\'t support the getAttribute function!');
7967      }
7968
7969      return undefined;
7970    }
7971
7972    return element.getAttribute(attribute);
7973  }
7974
7975  /**
7976   * Returns the value of a given element's attribute/property
7977   * @param {HTMLElement|EventTarget} element
7978   * @param {string} attribute
7979   * @param {string} value
7980   * @param {boolean} strict
7981   * @returns {*|this|string}
7982   */
7983  static setAttribute(element, attribute, value, strict = true) {
7984    if (strict && DomAccess.hasAttribute(element, attribute) === false) {
7985      throw new Error(`The required property "${attribute}" does not exist!`);
7986    }
7987
7988    if (typeof element.setAttribute !== 'function') {
7989      if (strict) {
7990        throw new Error(
7991          'This node doesn\'t support the setAttribute function!');
7992      }
7993
7994      return undefined;
7995    }
7996
7997    return element.setAttribute(attribute, value);
7998  }
7999
8000  /**
8001   * Returns the value of a given css property name of an element - without px
8002   *
8003   * @param {HTMLElement|EventTarget} element
8004   * @param {string} cssPropertyName
8005   * @param {boolean} strict
8006   * @returns {string}
8007   */
8008  static getCssPropertyWithoutPx(element, cssPropertyName, strict = true) {
8009    if (strict && !(element instanceof Element)) {
8010      throw new Error('Erstes Argument muss ein DOM-Element sein.');
8011    }
8012    if (strict && typeof cssPropertyName !== 'string') {
8013      throw new Error('Zweites Argument muss eine Zeichenkette sein.');
8014    }
8015
8016    return window.getComputedStyle(element).getPropertyValue(cssPropertyName).replace('px', '');
8017  }
8018
8019  /**
8020   * Returns the value of a given elements dataset entry
8021   *
8022   * @param {HTMLElement|EventTarget} element
8023   * @param {string} key
8024   * @param {boolean} strict
8025   * @returns {*|this|string}
8026   */
8027  static getDataAttribute(element, key, strict = true) {
8028    const keyWithoutData = key.replace(/^data(|-)/, '');
8029    const parsedKey = StringHelper.toLowerCamelCase(keyWithoutData, '-');
8030    if (!DomAccess.isNode(element)) {
8031      if (strict) {
8032        throw new Error('The passed node is not a valid HTML Node!');
8033      }
8034
8035      return undefined;
8036    }
8037
8038    if (typeof element.dataset === 'undefined') {
8039      if (strict) {
8040        throw new Error('This node doesn\'t support the dataset attribute!');
8041      }
8042
8043      return undefined;
8044    }
8045
8046    const attribute = element.dataset[parsedKey];
8047
8048    if (typeof attribute === 'undefined') {
8049      if (strict) {
8050        throw new Error(
8051          `The required data attribute "${key}" does not exist on ${element}!`);
8052      }
8053
8054      return attribute;
8055    }
8056
8057    return StringHelper.parsePrimitive(attribute);
8058  }
8059
8060  /**
8061   * Returns the selected element of a defined parent node
8062   * @param {HTMLElement|EventTarget} parentNode
8063   * @param {string} selector
8064   * @param {boolean} strict
8065   * @returns {HTMLElement}
8066   */
8067  static querySelector(parentNode, selector, strict = true) {
8068    if (strict && !DomAccess.isNode(parentNode)) {
8069      throw new Error('The parent node is not a valid HTML Node!');
8070    }
8071
8072    const element = parentNode.querySelector(selector) || false;
8073
8074    if (strict && element === false) {
8075      throw new Error(
8076        `The required element "${selector}" does not exist in parent node!`);
8077    }
8078
8079    return element;
8080  }
8081
8082  /**
8083   * Returns the selected elements of a defined parent node
8084   *
8085   * @param {HTMLElement|EventTarget} parentNode
8086   * @param {string} selector
8087   * @param {boolean} strict
8088   * @returns {NodeList|false}
8089   */
8090  static querySelectorAll(parentNode, selector, strict = true) {
8091    if (strict && !DomAccess.isNode(parentNode)) {
8092      throw new Error('The parent node is not a valid HTML Node!');
8093    }
8094
8095    let elements = parentNode.querySelectorAll(selector);
8096    if (elements.length === 0) {
8097      elements = false;
8098    }
8099
8100    if (strict && elements === false) {
8101      throw new Error(
8102        `At least one item of "${selector}" must exist in parent node!`);
8103    }
8104
8105    return elements;
8106  }
8107}
8108
8109// reinit on ajax event
8110(function bindAccordionReinitOnce() {
8111  if (document.__accordionReinitBound) return;
8112  document.__accordionReinitBound = true;
8113
8114  document.addEventListener('accordion:reinit', (e) => {
8115    const root = (e.detail && e.detail.root instanceof HTMLElement)
8116      ? e.detail.root
8117      : document;
8118
8119    root.querySelectorAll('.js-accordion').forEach((el) => {
8120      el.classList.remove('js-accordion-init');
8121      const acc = new CsAccordion(el);
8122      acc.init();
8123    });
8124  });
8125})();
8126
8127class CsAccordion extends Core {
8128
8129  // openItemOnLoad: count from zero, negative values won't open any item at all -> is the number behind id="accordion-trigger-XXX"
8130  // openMultipleItems: if true, the active item won't close on trigger
8131  static options = {
8132    animationSpeed: 500,
8133    openItemOnLoad: -1,
8134    openMultipleItems: false,
8135    openAllOnLoad: false,
8136    ignoreHash: false
8137  };
8138
8139  /**
8140   * toggle item
8141   */
8142  toggleItem(trigger) {
8143    const open = StringHelper.parsePrimitive(DomAccess.getAttribute(trigger, 'aria-expanded', false));
8144    const panel = document.getElementById(DomAccess.getAttribute(trigger, 'aria-controls'));
8145
8146    panel.style.transition = `height ${this.settingAnimationSpeed}ms ease`;
8147
8148    // toggle panel
8149    if (open) {
8150
8151      panel.style.height = panel.scrollHeight + 'px';
8152      panel.offsetHeight;
8153      panel.style.height = '0px';
8154
8155      setTimeout(() => {
8156        panel.setAttribute('hidden', '');
8157      }, this.settingAnimationSpeed);
8158
8159    } else {
8160
8161      // consider closing the active item
8162      if (!this.settingOpenMultipleItems) {
8163        const expandedTrigger = DomAccess.querySelector(this.el, '.js-accordion-trigger[aria-expanded="true"]', false);
8164        if (expandedTrigger) {
8165          this.toggleItem(expandedTrigger);
8166        }
8167      }
8168
8169      panel.removeAttribute('hidden');
8170
8171      // animate panel
8172      panel.style.height = '0px';
8173      panel.offsetHeight;
8174      panel.style.height = panel.scrollHeight + 'px';
8175    }
8176
8177    // toggle trigger attribute
8178    DomAccess.setAttribute(trigger, 'aria-expanded', `${!open}`);
8179
8180    // on open panel
8181    if (!open) {
8182      // Wait until the height transition finished to avoid wrong scroll position
8183      const onDone = (evt) => {
8184        if (evt && evt.propertyName !== 'height') return; // only once for height
8185        panel.removeEventListener('transitionend', onDone);
8186        // Scroll to the trigger (or use panel if you prefer)
8187        this.scrollToElement(trigger, 8);
8188      };
8189
8190      // If there's a transition, listen for it; else fallback immediately
8191      panel.addEventListener('transitionend', onDone, { once: true });
8192
8193      // Fallback timeout in case transitionend doesn't fire (display changes, etc.)
8194      window.setTimeout(() => {
8195        panel.removeEventListener('transitionend', onDone);
8196        this.scrollToElement(trigger, 8);
8197      }, this.settingAnimationSpeed + 50);
8198    }
8199  }
8200
8201  /**
8202   * open item from initial setting
8203   */
8204  openItemOnLoad() {
8205    const trigger = DomAccess.querySelector(this.el, '#tab-' + this.settingOpenItemOnLoad, false );
8206
8207    if (this.settingOpenItemOnLoad  < 0 || !trigger) {
8208      return;
8209    }
8210
8211    this.toggleItem(trigger);
8212  }
8213
8214  openAllOnLoad() {
8215    if (!this.settingOpenAllOnLoad) {
8216      return;
8217    }
8218
8219    const accordionTriggers = DomAccess.querySelectorAll(this.el, '[id^="tab-"]');
8220    Iterator.iterate(accordionTriggers, (trigger) => this.toggleItem(trigger));
8221  }
8222
8223  setOptionsFromDataAttribute() {
8224    const dataOptions = this.el.dataset.accordionOptions;
8225    const defaults = CsAccordion.options;
8226
8227    let config = {};
8228    if (dataOptions) {
8229      try {
8230        config = JSON.parse(dataOptions);
8231      } catch(error) {
8232        console.warn('data-accordion-options ist kein gültiges JSON:', error);
8233      }
8234    }
8235
8236    this.settingOpenMultipleItems = config.openMultipleItems ?? CsAccordion.options.openMultipleItems;
8237    this.settingAnimationSpeed    = config.animationSpeed    ?? CsAccordion.options.animationSpeed;
8238    this.settingOpenItemOnLoad    = config.openItemOnLoad    ?? CsAccordion.options.openItemOnLoad;
8239    this.settingIgnoreHash        = config.ignoreHash        ?? CsAccordion.options.ignoreHash;
8240    this.settingOpenAllOnLoad     = config.openAllOnLoad     ?? defaults.openAllOnLoad;
8241  }
8242
8243  isOwnTrigger(el) {
8244    return el.closest('.js-accordion') === this.el;
8245  }
8246
8247  /**
8248   * opens the panel that is currently in the url hash.
8249   * trigger must be in the same accordion
8250   */
8251  openFromHash() {
8252
8253    if (this.settingIgnoreHash) return false;
8254
8255    const id = window.location.hash.slice(1);
8256    if (!id) return false;
8257
8258    const targetTrigger = document.getElementById(id);
8259    if (!targetTrigger) return false;
8260
8261    if (!this.isOwnTrigger(targetTrigger)) return false;
8262
8263    const openTriggers = DomAccess.querySelectorAll(
8264      this.el,
8265      '.js-accordion-trigger[aria-expanded="true"]', false
8266    );
8267
8268    if (openTriggers) {
8269      Iterator.iterate(openTriggers, (trigger) => {
8270        if (trigger !== targetTrigger) {
8271          this.toggleItem(trigger);
8272        }
8273      });
8274    }
8275
8276    const alreadyOpen = StringHelper.parsePrimitive(
8277      DomAccess.getAttribute(targetTrigger, 'aria-expanded', false)
8278    );
8279
8280    if (!alreadyOpen) {
8281      this.toggleItem(targetTrigger);
8282    }
8283
8284    return true;
8285  }
8286
8287  isAlreadyInitialized() {
8288    return this.el.classList.contains('js-accordion-init');
8289  }
8290
8291  // -- Helpers ---------------------------------------------------------------
8292
8293  /** Get current header height (handles CSS-set heights) */
8294  getHeaderHeight() {
8295    const header = document.querySelector('header');
8296    if (!header) return 0;
8297    const rect = header.getBoundingClientRect();
8298    return rect.height || parseFloat(getComputedStyle(header).height) || 0;
8299  }
8300
8301  /** Smoothly scroll to an element accounting for a sticky/fixed header */
8302  scrollToElement(el, extraOffset = 8) {
8303    // Respect reduced motion
8304    const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
8305
8306    const headerH = this.getHeaderHeight();
8307    const rect = el.getBoundingClientRect();
8308    const absoluteTop = window.pageYOffset + rect.top;
8309    const targetY = Math.max(absoluteTop - headerH - extraOffset, 0);
8310
8311    window.scrollTo({
8312      top: targetY,
8313      behavior: prefersReduced ? 'auto' : 'smooth'
8314    });
8315  }
8316
8317  /**
8318   * init accordion
8319   */
8320  initAccordion() {
8321    if (this.isAlreadyInitialized()) return;
8322
8323    this.setOptionsFromDataAttribute();
8324
8325    const openedByHash = this.openFromHash();
8326    if (!openedByHash) {
8327      if (this.settingOpenMultipleItems && this.settingOpenAllOnLoad) {
8328        this.openAllOnLoad();
8329      } else {
8330        this.openItemOnLoad();
8331      }
8332    }
8333
8334    // click event handler for all accordion items
8335    const allTriggers = DomAccess.querySelectorAll(this.el, '.js-accordion-trigger');
8336    Iterator.iterate(allTriggers, (trigger) => {
8337      if (!this.isOwnTrigger(trigger)) return;
8338
8339      trigger.addEventListener('click', (e) => {
8340        e.stopPropagation();
8341        this.toggleItem(trigger);
8342        if (!this.settingIgnoreHash) {
8343          history.replaceState(null, '', `#${trigger.id}`);
8344        }
8345      });
8346    });
8347
8348    // add init class
8349    this.el.classList.add('js-accordion-init');
8350  }
8351
8352  init() {
8353    this.initAccordion();
8354  }
8355}
8356
8357class CsFlipText extends Core {
8358
8359  togglePanel(panel, open) {
8360    this.el.setAttribute('aria-expanded', open);
8361
8362    if (open) {
8363      panel.removeAttribute('hidden');
8364      panel.focus();
8365    } else {
8366      panel.setAttribute('hidden', '');
8367      this.el.focus();
8368    }
8369  }
8370
8371  init() {
8372    const panelId = this.el.getAttribute('aria-controls');
8373    const panel = document.getElementById(panelId);
8374
8375    this.el.addEventListener('click', () => {
8376      this.togglePanel(panel, true);
8377    });
8378
8379    document.addEventListener('keydown', (e) => {
8380      if (this.el.getAttribute('aria-expanded') === 'true') {
8381        if (e.key === 'Escape' || e.key === 'Enter') {
8382          this.togglePanel(panel, false);
8383        }
8384      }
8385    });
8386
8387    panel.addEventListener('click', () => {
8388      this.togglePanel(panel, false);
8389    });
8390  }
8391}
8392
8393class CsAccessibilityAnimation extends Core {
8394
8395  static options = {
8396    playAnimationsByDefault: true,
8397    cookieName: 'cs-animations',
8398    playIconClass: 'cs-icon-play-circle-solid',
8399    pauseIconClass: 'cs-icon-pause-circle'
8400  };
8401
8402  handleAnimations(startAnimation) {
8403    const body = document.querySelector('body');
8404    const splideSliders = document.getElementsByClassName('splide splide--home');
8405    const triggerButton = this.el;
8406
8407    if (startAnimation) {
8408
8409      // If there is a Progress Slider, a custom event is triggered
8410      if (splideSliders.length > 0) {
8411        const event = new CustomEvent('splideSlidersFound', {
8412          detail: {
8413            found: true
8414          }
8415        });
8416        document.dispatchEvent(event);
8417      }
8418
8419      // start css animation
8420      body.classList.remove('h-stop-animations');
8421
8422      // swap icon for trigger
8423      triggerButton.classList.remove('cs-icon-play-circle-solid');
8424      triggerButton.classList.add('cs-icon-pause-circle');
8425
8426      // set title and aria-label
8427      triggerButton.setAttribute('title', triggerButton.dataset.titleStopAnimations);
8428      triggerButton.setAttribute('aria-label', triggerButton.dataset.titleStopAnimations);
8429
8430      // set Cookie
8431      api.set(CsAccessibilityAnimation.options.cookieName, '1');
8432    } else {
8433
8434      // If there is a Progress Slider, a custom event is triggered
8435      if (splideSliders.length > 0) {
8436        const event = new CustomEvent('splideSlidersFound', {
8437          detail: {
8438            found: false
8439          }
8440        });
8441        document.dispatchEvent(event);
8442      }
8443
8444      // stop css animation immediately
8445      body.classList.add('h-stop-animations');
8446
8447      // swap icon for trigger
8448      triggerButton.classList.add('cs-icon-play-circle-solid');
8449      triggerButton.classList.remove('cs-icon-pause-circle');
8450
8451      // set title and aria-label
8452      triggerButton.setAttribute('title', triggerButton.dataset.titleStartAnimations);
8453      triggerButton.setAttribute('aria-label', triggerButton.dataset.titleStartAnimations);
8454
8455      // set Cookie
8456      api.set(CsAccessibilityAnimation.options.cookieName, '0');
8457    }
8458  }
8459
8460  initAnimations() {
8461    if (!api.get(CsAccessibilityAnimation.options.cookieName)) {
8462
8463      // Cookie is not set yet
8464      this.handleAnimations(CsAccessibilityAnimation.options.playAnimationsByDefault);
8465    } else {
8466
8467      // Cookie is set
8468      this.handleAnimations(api.get(CsAccessibilityAnimation.options.cookieName) === '1');
8469    }
8470  }
8471
8472  initToggleButton() {
8473    this.el.addEventListener('click', () => {
8474      this.handleAnimations(api.get(CsAccessibilityAnimation.options.cookieName) !== '1');
8475    });
8476  }
8477
8478  init() {
8479    this.initAnimations();
8480    this.initToggleButton();
8481  }
8482}
8483
8484class CsCookieBannerRenderer extends Core {
8485
8486  static options = {
8487    layerPosition: 'center',
8488    language: 'de-DE',
8489    initializeErrMsg: 'Cookie Banner: Fehler beim Initialisieren',
8490    loadConfigErrMsg: 'Cookie Banner: Fehler beim Laden der JSON-Konfiguration',
8491    gtmCookieCategories: ['statistics', 'marketing'],
8492    path: {
8493      configJson: '/_assets/7b3ba8bb3871137dffa4d9cccacf730f/JavaScript/config/config-cookie-banner.json',
8494      contrastVersionCss: '/_assets/7b3ba8bb3871137dffa4d9cccacf730f/Css/template-contrast.css?1750927915'
8495    },
8496    selector: {
8497      globalWrapper: '',
8498      accordionClasses: ['js-accordion', 'js-cscb-accordion'],
8499      accordionItemClass: 'accordion__item',
8500      checkboxContainerClass: 'js-cscb-checkbox-container',
8501      templateHeaderPlaceholder: '.js-cscb-header-placeholder',
8502      templateHeaderId: 'cscb-template-header',
8503      templateTitlePlaceholder: '.js-cscb-title-placeholder',
8504      templateTitleId: 'cscb-template-title',
8505      templateButtonGroupPlaceholder: '.js-cscb-button-group-placeholder',
8506      templateButtonGroupId: 'cscb-template-button-group',
8507      templateFooterPlaceholder: '.js-cscb-footer-placeholder',
8508      templateFooterId: 'cscb-template-footer',
8509      templateCategoriesWrapperClass: 'cscb-edit-box__settings-wrapper',
8510      templateCategoriesPlaceholder: '.js-cscb-categories-placeholder',
8511      templateCategoryId: 'cscb-template-category-setting',
8512      templateCategoryWrapperClass: 'cscb-edit-box__settings-item',
8513      templateCookieTablePlaceholder: '.js-cscb-cookie-tables',
8514      templateCookieTableId: 'cscb-template-cookie-table',
8515      templateMediaCheckboxPlaceholder: '.js-cscb-cookie-media-items',
8516      templateMediaCheckboxId: 'cscb-template-media-checkbox',
8517      templateLayerPlaceholder: '.js-cscb-layer-placeholder',
8518      templateLayerId: 'cscb-template-layer',
8519      templateMediaPreviewId: 'cscb-template-media-preview',
8520      templateMediaPreviewPlaceholderClass: 'js-cscb-media-preview-placeholder',
8521      modalTitleId: 'modal-cscb-headline',
8522      modalContentWrapperSelector: '.js-cscb-edit-box .js-cs-modal-content-wrapper'
8523    }
8524  };
8525
8526
8527
8528  /**
8529   * read settings
8530   */
8531  setLanguage() {
8532    this.language = document.getElementsByTagName('html')[0].getAttribute('lang');
8533    if (!this.language) {
8534      this.language = CsCookieBannerRenderer.options.language;
8535    }
8536  }
8537
8538  async loadSettings() {
8539    const response = await fetch(CsCookieBannerRenderer.options.path.configJson);
8540    if (!response.ok) throw new Error(CsCookieBannerRenderer.options.loadConfigErrMsg);
8541
8542    const configData = await response.json();
8543
8544    this.data = configData['language'][this.language];
8545  }
8546
8547
8548
8549  /**
8550   * general template rendering helper functions
8551   */
8552  renderTemplate(templateString, data = {}) {
8553    return templateString.replace(/\[\[([^\]]+)\]\]/g, (match, key) => {
8554      return typeof data[key] !== 'undefined' ? data[key] : '';
8555    });
8556  }
8557
8558  hydrateDataLinks(containerEl) {
8559    containerEl.querySelectorAll('a[data-href]').forEach(a => {
8560      a.href = a.dataset.href;
8561      a.removeAttribute('data-href');
8562    });
8563  }
8564  renderPartialToSelector(selector, htmlString, root = document) {
8565    const el = root.querySelector(selector);
8566    if (!el) {
8567      console.warn(`Placeholder ${selector} not found.`);
8568      return;
8569    }
8570
8571    const wrapper = document.createElement('div');
8572    wrapper.innerHTML = htmlString;
8573
8574    this.hydrateDataLinks(wrapper);
8575
8576    const fragment = document.createDocumentFragment();
8577    Array.from(wrapper.childNodes).forEach(node => fragment.appendChild(node));
8578
8579    el.replaceWith(fragment);
8580  }
8581
8582  insertPartialFromTemplate(templateId, data, placeholderSelector, root = document) {
8583
8584    // get template
8585    const template = document.getElementById(templateId);
8586    if (!template) return;
8587
8588    // fill template with data
8589    const rendered = this.renderTemplate(template.innerHTML, data);
8590
8591    this.renderPartialToSelector(placeholderSelector, rendered, root);
8592  }
8593
8594
8595
8596  /**
8597   * cookie categories: specific template rendering helper functions
8598   */
8599  renderMediaItems(categoryNode, mediaData = {}) {
8600    const mediaWrapper = categoryNode.querySelector(CsCookieBannerRenderer.options.selector.templateMediaCheckboxPlaceholder);
8601    if (!mediaWrapper) return;
8602
8603    const mediaKeys = Object.keys(mediaData);
8604    if (mediaKeys.length === 0) {
8605      mediaWrapper.remove();
8606      return;
8607    }
8608
8609    const template = document.getElementById(CsCookieBannerRenderer.options.selector.templateMediaCheckboxId);
8610    if (!template) return;
8611
8612    const renderedItems = mediaKeys.map(key => {
8613      const media = mediaData[key];
8614
8615      return this.renderTemplate(template.innerHTML, {
8616        mediaKey: key,
8617        label: media.label || '',
8618        urlToPrivacy: media.urlToPrivacy || '#',
8619        dataProtectionExternalName: this.data.dataProtectionExternalName,
8620        dataProtectionAriaLabel: this.data.dataProtectionAriaLabel + ' ' + media.label
8621      });
8622    }).join('');
8623
8624    this.renderPartialToSelector(
8625      CsCookieBannerRenderer.options.selector.templateMediaCheckboxPlaceholder,
8626      renderedItems,
8627      categoryNode
8628    );
8629  }
8630
8631  renderCookieTables(categoryNode, cookies = []) {
8632    const placeholder = categoryNode.querySelector(CsCookieBannerRenderer.options.selector.templateCookieTablePlaceholder);
8633    if (!placeholder) return;
8634
8635    if (!Array.isArray(cookies) || cookies.length === 0) {
8636      placeholder.remove();
8637      return;
8638    }
8639
8640    const template = document.getElementById(CsCookieBannerRenderer.options.selector.templateCookieTableId);
8641    if (!template) return;
8642
8643    const renderedTables = cookies.map(cookie => {
8644      const tableData = {
8645        nameTitle: this.data.nameTitle,
8646        name: cookie.name,
8647        link: cookie.link,
8648        dataProtectionExternalName: this.data.dataProtectionExternalName,
8649        purposeTitle: this.data.purposeTitle,
8650        purpose: cookie.purpose
8651      };
8652
8653      return this.renderTemplate(template.innerHTML, tableData);
8654    }).join('');
8655
8656    this.renderPartialToSelector(
8657      CsCookieBannerRenderer.options.selector.templateCookieTablePlaceholder,
8658      renderedTables,
8659      categoryNode
8660    );
8661  }
8662
8663  renderCategoryItem(categoryKey, categoryData) {
8664    const template = document.getElementById(CsCookieBannerRenderer.options.selector.templateCategoryId);
8665    if (!template) return '';
8666
8667    const data = {
8668      key: categoryKey,
8669      title: categoryData.title,
8670      contrastModeAdditionalTitle: categoryData.contrastModeAdditionalTitle,
8671      description: categoryData.description,
8672      tagManager: CsCookieBannerRenderer.options.gtmCookieCategories.includes(categoryKey) ? 'active' : 'inactive'
8673    };
8674
8675    // render item with data
8676    const categoryNode = document.createElement('div');
8677    categoryNode.innerHTML = this.renderTemplate(template.innerHTML, data);
8678    categoryNode.classList.add(
8679      CsCookieBannerRenderer.options.selector.templateCategoryWrapperClass,
8680      CsCookieBannerRenderer.options.selector.accordionItemClass,
8681      CsCookieBannerRenderer.options.selector.checkboxContainerClass
8682    );
8683
8684    // disable essential checkbox
8685    const checkboxEssential = categoryNode.querySelector('#cscb-essential');
8686    if (checkboxEssential) {
8687      checkboxEssential.disabled = true;
8688      checkboxEssential.checked = true;
8689    }
8690
8691    return categoryNode;
8692  }
8693
8694  renderCookieCategories(cookieSettings) {
8695    const placeholder = document.querySelector(CsCookieBannerRenderer.options.selector.templateCategoriesPlaceholder);
8696    if (!placeholder) return;
8697
8698    const wrapperNode = document.createElement('div');
8699    wrapperNode.classList.add(CsCookieBannerRenderer.options.selector.templateCategoriesWrapperClass);
8700    CsCookieBannerRenderer.options.selector.accordionClasses.forEac
8700h(className => {
8701      wrapperNode.classList.add(className);
8702    });
8703    wrapperNode.dataset.accordionOptions = JSON.stringify({
8704      ignoreHash: true
8705    });
8706
8707    placeholder.replaceWith(wrapperNode);
8708
8709    Object.entries(cookieSettings).forEach(([key, data]) => {
8710      const categoryNode = this.renderCategoryItem(key, data);
8711
8712      // render media checkboxes
8713      this.renderMediaItems(categoryNode, data.media);
8714
8715      // render table data (e.g. statistics)
8716      this.renderCookieTables(categoryNode, data.cookies);
8717
8718      // add cookie category to wrapper
8719      wrapperNode.appendChild(categoryNode);
8720    });
8721  }
8722
8723
8724
8725  /**
8726   * build cookie banner - main function
8727   */
8728  buildCookieBanner() {
8729    const settings = CsCookieBannerRenderer.options;
8730
8731    // insert header
8732    this.insertPartialFromTemplate(settings.selector.templateHeaderId, {
8733      closeButtonAriaLabel: this.data.closeButtonAriaLabel,
8734      contrastButtonIncreaseLabel: this.data.contrastButtonIncreaseLabel,
8735      contrastButtonResetLabel: this.data.contrastButtonResetLabel,
8736      contrastVersionPath: settings.path.contrastVersionCss
8737    }, settings.selector.templateHeaderPlaceholder);
8738
8739    // insert title and description
8740    this.insertPartialFromTemplate(settings.selector.templateTitleId, {
8741      headline: this.data.headline,
8742      infoText: this.data.infoText,
8743      modalTitleId: settings.selector.modalTitleId
8744    }, settings.selector.templateTitlePlaceholder);
8745
8746    // set aria-labelledby after title is inserted
8747    const modalContentWrapper = document.querySelector(settings.selector.modalContentWrapperSelector);
8748    if (modalContentWrapper) {
8749      modalContentWrapper.setAttribute('aria-labelledby', settings.selector.modalTitleId);
8750    }
8751
8752    // insert button group
8753    this.insertPartialFromTemplate(settings.selector.templateButtonGroupId, {
8754      buttonAcceptText: this.data.buttonAcceptText,
8755      buttonSaveText: this.data.buttonSaveText,
8756      buttonDeclineText: this.data.buttonDeclineText
8757    }, settings.selector.templateButtonGroupPlaceholder);
8758
8759    // insert footer
8760    this.insertPartialFromTemplate(settings.selector.templateFooterId, {
8761      dataProtectionLink: this.data.dataProtectionLink,
8762      dataProtectionName: this.data.dataProtectionName,
8763      imprintLink: this.data.imprintLink,
8764      imprintName: this.data.imprintName
8765    }, settings.selector.templateFooterPlaceholder);
8766
8767    // insert cookie categories
8768    this.renderCookieCategories(this.data.cookieSettings);
8769
8770    // insert layer template
8771    this.insertPartialFromTemplate(settings.selector.templateLayerId, {
8772      layerPosition: settings.layerPosition,
8773      layerInfoText: this.data.layerInfoText,
8774      buttonAcceptText: this.data.buttonAcceptText,
8775      buttonDeclineText: this.data.buttonDeclineText,
8776      buttonDenyText: this.data.buttonDenyText
8777    }, settings.selector.templateLayerPlaceholder);
8778
8779    // fire event
8780    document.dispatchEvent(new CustomEvent('cscb:built'));
8781  }
8782
8783  buildMediaPreviewBox(externalMediaElement, cookieName) {
8784    const settings = CsCookieBannerRenderer.options;
8785
8786    // create placeholder node
8787    const placeholderNode = document.createElement('div');
8788    placeholderNode.classList.add(settings.selector.templateMediaPreviewPlaceholderClass);
8789    externalMediaElement.insertAdjacentElement('afterend', placeholderNode);
8790
8791    // insert data if provided
8792    const previewBoxData = this.data.cookieSettings.externalMedia?.media[cookieName]?.previewBox;
8793    if (!previewBoxData) return;
8794
8795    const placeholderSelector = `.${settings.selector.templateMediaPreviewPlaceholderClass}`;
8796    this.insertPartialFromTemplate(settings.selector.templateMediaPreviewId, {
8797      description: previewBoxData.description,
8798      buttonText: previewBoxData.button,
8799      cookieKey: cookieName
8800    }, placeholderSelector);
8801
8802    // fire event
8803    document.dispatchEvent(new CustomEvent('cscb:preview-built'));
8804  }
8805
8806
8807
8808  /**
8809   * main init function
8810   */
8811  init() {
8812    this.setLanguage();
8813
8814    this.loadSettings().then(() => {
8815      this.buildCookieBanner();
8816    }).catch(error => {
8817      console.warn(CsCookieBannerRenderer.options.initializeErrMsg, error);
8818
8819      // rejects all cookies in case of an initialization error
8820      this.cookieBannerHandler = PluginController.retrievePluginInstanceFromElement(document, 'CsModalManager');
8821      if (!this.cookieBannerHandler) return;
8822
8823      this.cookieBannerHandler.declineAllCookies();
8824    });
8825  }
8826}
8827
8828class CsCookieBannerHandler extends Core {
8829
8830  /**
8831   * options
8832   */
8833  static options = {
8834
8835    modalName: 'cscb-cookie-banner', // must match the name in the template (trigger and modal)
8836    openModalOnStart: true,
8837    openModalOnExternalMediaFrame: true,
8838    openModalIgnoreUrlList: ['intranet.bbw-leipzig.de', 'bbw-intranet-13.ddev.site'],
8839    cookieName: 'cs-cookie-banner',
8840    expireDays: 30,
8841    gtmCookieText: 'gtm',
8842    gtmMainCategory: 'statistics',
8843    loadGtmConfigErrMsg: 'Cookie Banner: Fehler beim Laden der GTM-JSON-Konfiguration',
8844    path: {
8845      gtmConfigJson: '/_assets/7b3ba8bb3871137dffa4d9cccacf730f/JavaScript/config/config-gtm-domain-keys.json',
8846    },
8847    selector: {
8848      firstFocusableElement: 'body a:first-of-type',
8849      contrastButtonPage: '.js-toggle-high-contrast',
8850      showClass: 'h-show',
8851      animationClass: 'h-animate',
8852      cookieBannerModal: '.js-cscb-edit-box',
8853      cookieBannerLayer: '.js-cscb-info-box',
8854      contrastButton: '.js-cscb-toggle-high-contrast',
8855      checkbox: '.js-cscb-checkbox',
8856      checkboxParentClass: 'js-cscb-checkbox--parent',
8857      checkboxChildClass: 'js-cscb-checkbox--child',
8858      acceptAllCookies: '.js-cscb-accept-all-cookies',
8859      declineAllCookies: '.js-cscb-decline-cookies',
8860      customizeSettings: '.js-cscb-open-edit-box',
8861      saveSettings: '.js-cscb-save-cookie-settings',
8862      externalMedia: '.js-cscb-external-media',
8863      externalMediaWrapper: '.js-cscb-preview-box-wrapper',
8864      externalMediaWrapperPreviewBoxIsVisibleClass: 'preview-box-is-visible',
8865      externalMediaPreviewBoxClass: 'js-cscb-preview-box',
8866      externalMediaAcceptButtonClass: 'js-cscb-preview-box-button',
8867      externalMediaAccordionTriggerId: 'cscb-accordion-trigger-externalMedia',
8868      externalMediaLoadingClass: 'has-loader',
8869      externalMediaLiveRegion: '.js-cscb-preview-box-live-region'
8870    },
8871    cssVariables: {
8872      cookieBannerAnimationTime: '--cscb-default-transition-time'
8873    }
8874  };
8875
8876
8877
8878  /**
8879   * helper functions
8880   */
8881  reserveSpaceForCookieBannerLayer(stateOpen = true) {
8882    const cookieBannerLayer = document.querySelector(CsCookieBannerHandler.options.selector.cookieBannerLayer);
8883    const bodyOffset = stateOpen ? cookieBannerLayer.offsetHeight : 0;
8884    const body = document.querySelector('body');
8885    if (body) {
8886      body.style.marginBottom = `${bodyOffset}px`;
8887    }
8888  }
8889
8890  isUrlOnIgnoreList() {
8891    const ignoreList = CsCookieBannerHandler.options.openModalIgnoreUrlList;
8892    if (!Array.isArray(ignoreList) || ignoreList.length === 0) return false;
8893
8894    const currentUrl = window.location.href;
8895    return ignoreList.some(url => currentUrl.includes(url));
8896  }
8897
8898
8899
8900  /**
8901   * cookie settings - init content
8902   */
8903  initializeAccordion() {
8904
8905    const accordionSelector = CsCookieBannerRenderer.options.selector.accordionClasses.map(cls => `.${cls}`).join('');
8906    const accordionNode = document.querySelector(accordionSelector);
8907    if (accordionNode) {
8908      PluginController.initializePlugin('CsAccordion', accordionNode);
8909    }
8910  }
8911
8912  initializeCheckboxes() {
8913    const cookieString = api.get(CsCookieBannerHandler.options.cookieName);
8914    if (!cookieString) return;
8915
8916    const values = cookieString.split('|').filter(key => key && key !== 'gtm');
8917
8918    values.forEach(key => {
8919      const checkbox = document.getElementById(`cscb-${key}`);
8920      if (checkbox) {
8921        checkbox.checked = true;
8922      }
8923    });
8924  }
8925
8926  initializeCookieBannerContent() {
8927    this.initializeAccordion();
8928    this.initializeCheckboxes();
8929  }
8930
8931
8932
8933  /**
8934   * cookie settings - checkbox handling
8935   */
8936  handleCheckboxChildrenStatePropagation(target) {
8937    const container = target.closest('.' + CsCookieBannerRenderer.options.selector.checkboxContainerClass);
8938    const childCheckboxes = container?.querySelectorAll('.' + CsCookieBannerHandler.options.selector.checkboxChildClass);
8939    if (childCheckboxes?.length) {
8940      childCheckboxes.forEach(child => {
8941        child.checked = target.checked;
8942      });
8943    }
8944  }
8945
8946  handleCheckboxParentStatePropagation(target) {
8947    const wrapper = target.closest('.' + CsCookieBannerRenderer.options.selector.checkboxContainerClass);
8948    if (!wrapper) return;
8949
8950    const parentCheckbox = wrapper.querySelector('.' + CsCookieBannerHandler.options.selector.checkboxParentClass);
8951    const childCheckboxes = wrapper.querySelectorAll('.' + CsCookieBannerHandler.options.selector.checkboxChildClass);
8952
8953    if (!parentCheckbox || !childCheckboxes.length) return;
8954
8955    const allUnchecked = Array.from(childCheckboxes).every(cb => !cb.checked);
8956
8957    parentCheckbox.checked = !allUnchecked;
8958  }
8959
8960  activateAllCheckboxes() {
8961    const checkboxes = document.querySelectorAll(CsCookieBannerHandler.options.selector.checkbox);
8962    checkboxes.forEach(cb => cb.checked = true);
8963  }
8964
8965  deactivateAllCheckboxes() {
8966    const checkboxes = document.querySelectorAll(CsCookieBannerHandler.options.selector.checkbox);
8967    checkboxes.forEach(cb => {
8968      if (cb.id !== 'cscb-essential') {
8969        cb.checked = false;
8970      }
8971    });
8972  }
8973
8974
8975
8976  /**
8977   * google tag manager (gtm) handling
8978   */
8979  async loadGtmSettings() {
8980    const response = await fetch(CsCookieBannerHandler.options.path.gtmConfigJson);
8981    if (!response.ok) throw new Error(CsCookieBannerHandler.options.loadGtmConfigErrMsg);
8982
8983    const configData = await response.json();
8984
8985    this.gtmData = configData['googleTagManagerKeys'];
8986  }
8987
8988  initDataLayer() {
8989    if (this._tagManagerDataLayerSet) return;
8990    window.dataLayer = window.dataLayer || [];
8991    window.gtag = window.gtag || function(){ window.dataLayer.push(arguments); };
8992    this._tagManagerDataLayerSet = true;
8993  }
8994
8995  setGoogleTag(method = 'default', value = 'denied', command = 'consent') {
8996    // eslint-disable-next-line
8997    gtag(command, method, {
8998      ad_user_data: value,
8999      ad_personalization: value,
9000      ad_storage: value,
9001      analytics_storage: value
9002    });
9003  }
9004
9005  setTagManagerDefaults() {
9006    this.initDataLayer();
9007    if (!this._consentDefaultSet) {
9008      this.setGoogleTag();
9009      this._consentDefaultSet = true;
9010    }
9011  }
9012
9013  revokeTagManager() {
9014    this.initDataLayer();
9015    this.setGoogleTag('update', 'denied');
9016    this._gtmLoaded = false;
9017  }
9018
9019  loadGtmScript(gtmContainerId) {
9020    if (this._gtmLoaded || document.querySelector('script[src*="googletagmanager.com/gtm.js"]')) return;
9021
9022    this.initDataLayer();
9023    window.dataLayer.push({ 'gtm.start': Date.now(), event: 'gtm.js' });
9024
9025    const s = document.createElement('script');
9026    s.async = true;
9027    s.src = `https://www.googletagmanager.com/gtm.js?id=${encodeURIComponent(gtmC
9027ontainerId)}`;
9028    document.head.appendChild(s);
9029
9030    this._gtmLoaded = true;
9031  }
9032
9033  loadTagManager() {
9034
9035    const { cookieName, gtmCookieText } = CsCookieBannerHandler.options;
9036
9037    // set defaults
9038    this.setTagManagerDefaults();
9039
9040    // check if user accepted gtm cookie
9041    const cookieString = api.get(cookieName) || '';
9042    const hasConsent = cookieString.includes(gtmCookieText);
9043
9044    // no consent
9045    if (!hasConsent) {
9046      this.revokeTagManager();
9047      return;
9048    }
9049
9050    // set consent
9051    this.setGoogleTag('update', 'granted');
9052
9053    // get gtm container id
9054    let domain = window.location.hostname;
9055    const gtmContainerId = this.gtmData?.[domain] || null;
9056    if (!gtmContainerId || !/^GTM-\w+/.test(gtmContainerId)) {
9057      console.warn('GTM container ID ist ungültig oder fehlt: ', gtmContainerId);
9058      return;
9059    }
9060
9061    // load gtm script
9062    this.loadGtmScript(gtmContainerId);
9063  }
9064
9065
9066
9067  /**
9068   * external media content
9069   */
9070  screenreaderAnnouncement(wrapper, message = '') {
9071    const region = wrapper?.querySelector(CsCookieBannerHandler.options.selector.externalMediaLiveRegion);
9072    if (!region) return;
9073
9074    region.textContent = '';
9075    requestAnimationFrame(() => { region.textContent = message; });
9076  }
9077
9078  showExternalMediaContent(externalMediaElement) {
9079    if (!externalMediaElement) return;
9080
9081    const wrapper = externalMediaElement.closest(CsCookieBannerHandler.options.selector.externalMediaWrapper);
9082    const message = wrapper?.dataset.srActivateMessage;
9083    const isIframe = externalMediaElement instanceof HTMLIFrameElement || externalMediaElement.tagName === 'IFRAME';
9084
9085    // add loading state while the external media element is fetching its content
9086    if (wrapper) wrapper.classList.add(CsCookieBannerHandler.options.selector.externalMediaLoadingClass);
9087
9088    if (isIframe) {
9089      const src = externalMediaElement.dataset.src;
9090      if (!src) return;
9091
9092      // remove loading class once the external media element has finished loading
9093      externalMediaElement.addEventListener('load', () => {
9094
9095        // remove loading class
9096        if (wrapper) wrapper.classList.remove(CsCookieBannerHandler.options.selector.externalMediaLoadingClass);
9097
9098        // include in focus tab order
9099        externalMediaElement.tabIndex = 0;
9100
9101        // live region announcement
9102        this.screenreaderAnnouncement(wrapper, message);
9103
9104        // focus on iframe
9105        externalMediaElement.focus();
9106      }, { once: true }); // prevents stacking multiple listeners on repeated calls
9107
9108      // set src from data-src
9109      externalMediaElement.setAttribute('src', src);
9110      externalMediaElement.removeAttribute('data-src');
9111
9112      // show the iframe
9113      externalMediaElement.hidden = false;
9114    } else {
9115      // remove loading class
9116      if (wrapper) wrapper.classList.remove(CsCookieBannerHandler.options.selector.externalMediaLoadingClass);
9117
9118      // live region announcement
9119      this.screenreaderAnnouncement(wrapper, message);
9120
9121      // show the external media element
9122      externalMediaElement.hidden = false;
9123    }
9124  }
9125
9126  removeExternalMediaContent(externalMediaElement) {
9127
9128    // for iframes
9129    if (externalMediaElement.hasAttribute('src')) {
9130      // remove src and set data-src (e.g. if iframe)
9131      const src = externalMediaElement.getAttribute('src');
9132      externalMediaElement.setAttribute('data-src', src);
9133      externalMediaElement.removeAttribute('src');
9134
9135      externalMediaElement.tabIndex = -1;
9136    }
9137
9138    // hide the external media element
9139    externalMediaElement.hidden = true;
9140  }
9141
9142  refreshExternalMediaContent() {
9143    const cookieString = api.get(CsCookieBannerHandler.options.cookieName);
9144
9145    const externalMediaElements = document.querySelectorAll(CsCookieBannerHandler.options.selector.externalMedia);
9146
9147    externalMediaElements.forEach(externalMediaElement => {
9148      const cookieName = externalMediaElement.dataset.cookieName;
9149      if (!cookieName) {
9150        console.warn('No cookie name defined for media element (e.g. check "CookieBannerMedia.html")');
9151        return;
9152      }
9153
9154      const externalMediaElementConfig = this.cookieBannerRenderer.data.cookieSettings.externalMedia.media[cookieName];
9155      const wrapper = externalMediaElement.closest(CsCookieBannerHandler.options.selector.externalMediaWrapper);
9156
9157      if (externalMediaElementConfig.previewBox) {
9158        const previewBox = externalMediaElement.nextElementSibling;
9159        if (cookieString && cookieString.includes(cookieName)) {
9160
9161          // show content
9162          this.showExternalMediaContent(externalMediaElement);
9163
9164          // remove preview box
9165          if (previewBox && previewBox.classList.contains(CsCookieBannerHandler.options.selector.externalMediaPreviewBoxClass)) {
9166            previewBox.remove();
9167          }
9168          if (wrapper) wrapper.classList.remove(CsCookieBannerHandler.options.selector.externalMediaWrapperPreviewBoxIsVisibleClass);
9169        } else {
9170
9171          // remove content
9172          this.removeExternalMediaContent(externalMediaElement);
9173
9174          // create preview box
9175          if (!previewBox || !previewBox.classList.contains(CsCookieBannerHandler.options.selector.externalMediaPreviewBoxClass)) {
9176            this.cookieBannerRenderer.buildMediaPreviewBox(externalMediaElement, cookieName);
9177          }
9178          if (wrapper) wrapper.classList.add(CsCookieBannerHandler.options.selector.externalMediaWrapperPreviewBoxIsVisibleClass);
9179        }
9180      }
9181
9182    });
9183  }
9184
9185  customFunctions = {
9186    activateGoogleMapsExt: () => {
9187      const googleMapsDivs = document.querySelectorAll('div[data-cookie-name="maps_google"]');
9188
9189      googleMapsDivs.forEach((div) => {
9190        const libraryUrl = div.dataset.library;
9191
9192        if (libraryUrl) {
9193          const script = document.createElement('script');
9194          script.src = libraryUrl;
9195          document.body.appendChild(script);
9196
9197          document.querySelector('.js-gme-container').removeAttribute('style');
9198        }
9199      });
9200    },
9201    activateSmartsupp: () => {
9202      const smartsuppDiv = document.querySelector('div[data-cookie-name="chat_smartsupp"]');
9203
9204      if (smartsuppDiv) {
9205        const smartsuppCode = `
9206          var _smartsupp = _smartsupp || {};
9207          _smartsupp.key = '4b057508eb9b06f5cb0d4d8d087669bb0a8535a7';
9208          window.smartsupp || (function (d) {
9209            var s, c, o = smartsupp = function () { o._.push(arguments) }; o._ = [];
9210            s = d.getElementsByTagName('script')[0]; c = d.createElement('script');
9211            c.type = 'text/javascript'; c.charset = 'utf-8'; c.async = true;
9212            c.src = 'https://www.smartsuppchat.com/loader.js?';
9213            s.parentNode.insertBefore(c, s);
9214          })(document);
9215        `;
9216
9217        const scriptEl = document.createElement('script');
9218        scriptEl.type = 'text/javascript';
9219        scriptEl.text = smartsuppCode;
9220
9221        document.body.appendChild(scriptEl);
9222
9223        // @TODO magnific was replaced by tobii
9224        // eslint-disable-next-line
9225        if (window.jQuery && $.magnificPopup) {
9226          // eslint-disable-next-line
9227          $.magnificPopup.close();
9228        }
9229      }
9230    },
9231    activateTrustYou: () => {
9232      const trustYouIframes = document.querySelectorAll('iframe[data-cookie-name="trust_you"]');
9233
9234      trustYouIframes.forEach((iframe) => {
9235        const libraryUrl = iframe.dataset.library;
9236
9237        // If library URL is present, load resizer
9238        if (libraryUrl) {
9239          const script = document.createElement('script');
9240          script.src = libraryUrl;
9241          document.body.appendChild(script);
9242        }
9243
9244        // Wait a couple of milliseconds before calling the resize function,
9245        // because the iframe is loaded asynchronously
9246        setTimeout(() => {
9247
9248          if (typeof iFrameResize === 'function') {
9249            // eslint-disable-next-line
9250            iFrameResize({ log: false }, iframe);
9251          } else {
9252            console.warn('iFrameResize function not found. Is the library loaded?');
9253          }
9254        }, 500);
9255      });
9256    },
9257    activateHotelSpider: () => {
9258
9259      const hotelSpiderDiv = document.querySelector('div[data-cookie-name="hotelspider_cookie"]');
9260
9261      if (hotelSpiderDiv) {
9262        // load CSS
9263        const linkHotelSpider = document.createElement('link');
9264        linkHotelSpider.rel = 'stylesheet';
9265        linkHotelSpider.href = 'https://wbe-static.hotel-spider.com/widget/spiderBooking4.css';
9266        linkHotelSpider.type = 'text/css';
9267        document.head.appendChild(linkHotelSpider);
9268
9269        // load JS
9270        const scriptHotelSpider = document.createElement('script');
9271        scriptHotelSpider.src = 'https://wbe-static.hotel-spider.com/widget/spiderBooking4.js';
9272        scriptHotelSpider.async = true;
9273        scriptHotelSpider.onload = function () {
9274          // eslint-disable-next-line
9275          if (typeof jQuery.fn.SpiderBooking4 === 'function') {
9276            const spiderBookingContainer = document.getElementById('spiderBooking');
9277            if (spiderBookingContainer) {
9278              // eslint-disable-next-line
9279              jQuery(spiderBookingContainer).SpiderBooking4({
9280                hotelId: '02H5e00cb52afeb0',
9281                template: spiderBookingContainer.dataset.template,
9282                lang: spiderBookingContainer.dataset.lang
9283              });
9284            }
9285          }
9286        };
9287        document.head.appendChild(scriptHotelSpider);
9288      }
9289    }
9290  };
9291
9292  loadExternalScript(url) {
9293    return new Promise((resolve, reject) => {
9294      // check if script is already loaded
9295      if (document.querySelector(`script[src="${url}"]`)) {
9296        resolve();
9297        return;
9298      }
9299
9300      // create script element
9301      const s = document.createElement('script');
9302      s.src = url;
9303      s.async = true;
9304      s.onload = () => resolve();
9305      s.onerror = () => reject(new Error(`Failed to load ${url}`));
9306      document.head.appendChild(s);
9307    });
9308  }
9309
9310  initAdditionalFunctions() {
9311    const externalMediaElementConfig = this.cookieBannerRenderer.data.cookieSettings.externalMedia.media;
9312    const cookieString = api.get(CsCookieBannerHandler.options.cookieName);
9313
9314    Object.entries(externalMediaElementConfig).forEach(([cookieName, item]) => {
9315      if (item.function && this.customFunctions[item.function] && cookieString && cookieString.includes(cookieName)) {
9316        this.customFunctions[item.function]();
9317      }
9318
9319      if (item.urlToScript && cookieString && cookieString.includes(cookieName)) {
9320        this.loadExternalScript(item.urlToScript).catch(err => console.warn(err));
9321      }
9322    });
9323  }
9324
9325
9326
9327  /**
9328   * cookie persistence (cookie string handling)
9329   */
9330  saveCookies() {
9331    const checkboxes = document.querySelectorAll(CsCookieBannerHandler.options.selector.checkbox);
9332    const selectedKeys = [];
9333
9334    checkboxes.forEach(cb => {
9335      if (cb.checked) {
9336        const id = cb.id || '';
9337        if (id.startsWith('cscb-')) {
9338          const key = id.replace('cscb-', '');
9339          selectedKeys.push(key);
9340        }
9341      }
9342    });
9343
9344    if (selectedKeys.includes(this.options.gtmMainCategory) && !selectedKeys.includes(CsCookieBannerHandler.options.gtmCookieText)) {
9345      selectedKeys.push(CsCookieBannerHandler.options.gtmCookieText);
9346    }
9347
9348    const cookieValue = `|${selectedKeys.join('|')}|`;
9349
9350    api.set(CsCookieBannerHandler.options.cookieName, cookieValue, {
9351      expires: CsCookieBannerHandler.options.expireDays,
9352      path: '/'
9353    });
9354
9355    this.loadTagManager();
9356
9357    this.refreshExternalMediaContent();
9358
9359    this.initAdditionalFunctions();
9360  }
9361
9362
9363
9364  /**
9365   * event handling (interaction logic)
9366   */
9367  handleContrastToggle() {
9368    const contrastButtonPageSelector = document.querySelector(CsCookieBannerHandler.options.selector.contrastButtonPage);
9369    if (!contrastButtonPageSelector) return;
9370
9371    contrastButtonPageSelector.click();
9372  }
9373
9374  handleCheckboxChange(target) {
9375    if (target.classList.contains(CsCookieBannerHandler.options.selector.checkboxParentClass)) {
9376      this.handleCheckboxChildrenStatePropagation(target);
9377    }
9378
9379    if (target.classList.contains(CsCookieBannerHandler.options.selector.checkboxChildClass)) {
9380      this.handleCheckboxParentStatePropagation(target);
9381    }
9382  }
9383
9384  acceptAllCookies() {
9385    this.activateAllCheckboxes();
9386    this.saveCookies();
9387
9388    const cookieBannerLayer = document.querySelector(CsCookieBannerHandler.options.selector.cookieBannerLayer);
9389    if (cookieBannerLayer) {
9390      this.closeCookieBannerLayer();
9391    }
9392  }
9393
9394  acceptExternalMediaCookie(trigger) {
9395
9396    const cookieKey = trigger.dataset.csCookieKey;
9397    const checkboxId = `cscb-${cookieKey}`;
9398    const checkbox = document.getElementById(checkboxId);
9399
9400    if (this.options.openModalOnExternalMediaFrame) {
9401
9402      // open modal
9403      if (this.modalManager) {
9404        this.modalManager.open(CsCookieBannerHandler.options.modalName, false, trigger);
9405      }
9406
9407      // trigger accordion item, if it is not expanded
9408      const accordionTrigger = document.getElementById(CsCookieBannerHandler.options.selector.externalMediaAccordionTriggerId);
9409      if (accordionTrigger && accordionTrigger.getAttribute('aria-expanded') === 'false') {
9410        accordionTrigger.click();
9411      }
9412
9413      // scroll to checkbox
9414      checkbox.scrollIntoView({
9415        behavior: 'smooth'
9416      });
9417
9418      // focus checkbox after accordion item has opened
9419      requestAnimationFrame(() => {
9420        checkbox.focus();
9421      });
9422
9423    } else {
9424
9425      // set checkbox and save cookies
9426      checkbox.checked = true;
9427      this.handleCheckboxChange(checkbox);
9428      this.saveCookies();
9429    }
9430  }
9431
9432  declineAllCookies() {
9433    this.deactivateAllCheckboxes();
9434    this.saveCookies();
9435
9436    const cookieBannerLayer = document.querySelector(CsCookieBannerHandler.options.selector.cookieBannerLayer);
9437    if (cookieBannerLayer) {
9438      this.closeCookieBannerLayer();
9439    }
9440  }
9441
9442  customizeSettings(trigger) {
9443
9444    // open modal
9445    if (this.modalManager) {
9446      this.modalManager.open(CsCookieBannerHandler.options.modalName, true, trigger);
9447    }
9448  }
9449
9450  saveSettings() {
9451    this.saveCookies();
9452  }
9453
9454
9455
9456  /**
9457   * event binding (listeners)
9458   */
9459  bindModalOpenEvent() {
9460    document.addEventListener('cs-modal-open', () => {
9461
9462      // close cookie banner layer
9463      this.closeCookieBannerLayer(false);
9464    });
9465  }
9466
9467  bindCloseEvent() {
9468    /* modal close is handled by modal manager,
9469     * open cookie banner layer if no cookies are set
9470     * and modal does not open on start
9471     * or it does open, but is on ignore list for this url
9472     */
9473    document.addEventListener('cs-modal-close', (e) => {
9474      const { name: modalName, triggerLink } = e.detail;
9475      if (modalName !== 'cscb-cookie-banner') return;
9476
9477      // background layer (= modal) and close button have dismiss event
9478      // triggerLink is empty for escape key
9479      if (triggerLink?.dataset.cscbDismissEvent === 'true' || !triggerLink) {
9480        const cookieString = api.get(CsCookieBannerHandler.options.cookieName);
9481        if (cookieString || (CsCookieBannerHandler.options.openModalOnStart && !this.isUrlOnIgnoreList(
9481))) return;
9482
9483        this.openCookieBannerLayer();
9484      }
9485    });
9486  }
9487
9488  bindContrastButton() {
9489    const contrastBtn = document.querySelector(CsCookieBannerHandler.options.selector.contrastButton);
9490    if (contrastBtn) {
9491      contrastBtn.addEventListener('click', () => {
9492        this.handleContrastToggle();
9493      });
9494    }
9495  }
9496
9497  bindCheckboxEvents() {
9498    const checkboxes = document.querySelectorAll(CsCookieBannerHandler.options.selector.checkbox);
9499    checkboxes.forEach(checkbox => {
9500      checkbox.addEventListener('change', (event) => {
9501        this.handleCheckboxChange(event.target);
9502      });
9503    });
9504  }
9505
9506  bindConsentButtons() {
9507    const acceptButtons = document.querySelectorAll(CsCookieBannerHandler.options.selector.acceptAllCookies);
9508    const declineButtons = document.querySelectorAll(CsCookieBannerHandler.options.selector.declineAllCookies);
9509    const customizeSettingsButton = document.querySelector(CsCookieBannerHandler.options.selector.customizeSettings);
9510    const saveSettingsBtn = document.querySelector(CsCookieBannerHandler.options.selector.saveSettings);
9511    const acceptExternalMediaFrame = document.querySelectorAll(CsCookieBannerHandler.options.selector.externalMediaWrapper);
9512
9513    acceptExternalMediaFrame.forEach(acceptFrame => {
9514      acceptFrame.addEventListener('click', (e) => {
9515        if (e.target.classList.contains(CsCookieBannerHandler.options.selector.externalMediaAcceptButtonClass)) {
9516          this.acceptExternalMediaCookie(e.target);
9517        }
9518      });
9519    });
9520
9521    acceptButtons.forEach(acceptBtn => {
9522      acceptBtn.addEventListener('click', () => {
9523        this.acceptAllCookies();
9524      });
9525    });
9526
9527    declineButtons.forEach(declineBtn => {
9528      declineBtn.addEventListener('click', () => {
9529        this.declineAllCookies();
9530      });
9531    });
9532
9533    customizeSettingsButton.addEventListener('click', () => {
9534      this.customizeSettings(customizeSettingsButton);
9535    });
9536
9537    if (saveSettingsBtn) saveSettingsBtn.addEventListener('click', () => {
9538      this.saveSettings();
9539    });
9540  }
9541
9542  bindEvents() {
9543
9544    // modal events
9545    this.modalManager.bindCloseSelectors(document.querySelector(this.options.selector.cookieBannerModal));
9546
9547    // specific cscb events
9548    this.bindCloseEvent();
9549    this.bindContrastButton();
9550    this.bindCheckboxEvents();
9551    this.bindConsentButtons();
9552    this.bindModalOpenEvent();
9553  }
9554
9555
9556
9557  /**
9558   * display state for layer and modal
9559   */
9560  openCookieBannerModal() {
9561    if (this.modalManager) {
9562      this.modalManager.open(CsCookieBannerHandler.options.modalName);
9563    }
9564  }
9565
9566  toggleCookieBannerLayer(stateOpen = true, setCloseFocus = true) {
9567    const cookieBannerLayer = document.querySelector(CsCookieBannerHandler.options.selector.cookieBannerLayer);
9568    if (!cookieBannerLayer) return;
9569
9570    if (stateOpen) {
9571
9572      // open layer
9573      cookieBannerLayer.classList.add(CsCookieBannerHandler.options.selector.showClass);
9574      requestAnimationFrame(() => {
9575        cookieBannerLayer.classList.add(CsCookieBannerHandler.options.selector.animationClass);
9576      });
9577    } else {
9578
9579      // close layer
9580      cookieBannerLayer.classList.remove(CsCookieBannerHandler.options.selector.animationClass);
9581      setTimeout(() => {
9582        cookieBannerLayer.classList.remove(CsCookieBannerHandler.options.selector.showClass);
9583      }, CssProperties.getCssTimeProperty(CsCookieBannerHandler.options.cssVariables.cookieBannerAnimationTime));
9584
9585      // focus first element in body, if set
9586      if (setCloseFocus) {
9587        const firstFocusableElement = document.querySelector(CsCookieBannerHandler.options.selector.firstFocusableElement);
9588        if (firstFocusableElement) {
9589          firstFocusableElement.focus();
9590        }
9591      }
9592    }
9593
9594    // add or remove space
9595    this.reserveSpaceForCookieBannerLayer(stateOpen);
9596  }
9597
9598  closeCookieBannerLayer(setFocus = true) {
9599    this.toggleCookieBannerLayer(false, setFocus);
9600  }
9601
9602  openCookieBannerLayer() {
9603    this.toggleCookieBannerLayer();
9604  }
9605
9606  openCookieBanner() {
9607    const cookieString = api.get(CsCookieBannerHandler.options.cookieName);
9608    if (cookieString) return;
9609
9610    if (CsCookieBannerHandler.options.openModalOnStart && !this.isUrlOnIgnoreList(
9610)) {
9611      this.openCookieBannerModal();
9612    } else {
9613      this.openCookieBannerLayer();
9614    }
9615  }
9616
9617
9618
9619  /**
9620   * main init function
9621   */
9622  init() {
9623    document.addEventListener('cscb:built', () => {
9624      this.modalManager = PluginController.retrievePluginInstanceFromElement(document, 'CsModalManager');
9625      this.cookieBannerRenderer = PluginController.retrievePluginInstanceFromElement(this.el, 'CsCookieBannerRenderer');
9626
9627      this.initializeCookieBannerContent();
9628      this.bindEvents();
9629      this.openCookieBanner();
9630      this.refreshExternalMediaContent();
9631      this.initAdditionalFunctions();
9632
9633      this.loadGtmSettings().then(() => {
9634        this.loadTagManager();
9635      }).catch(error => {
9636        console.warn('Could not load Google Tag Manager keys.', error);
9637      });
9638
9639      window.addEventListener('resize', () => {
9640        const layer = document.querySelector(CsCookieBannerHandler.options.selector.cookieBannerLayer);
9641        if (layer && layer.classList.contains(CsCookieBannerHandler.options.selector.showClass)) {
9642          this.reserveSpaceForCookieBannerLayer(true);
9643        }
9644      });
9645
9646      document.dispatchEvent(new CustomEvent('cscb:initialized'));
9647    });
9648  }
9649}
9650
9651// core feature do not remove this line
9652
9653// register plugins for initializing
9654PluginController.addPlugin('CsModalManager', CsModalManager);
9655
9656PluginController.addPlugin('CsPhoneLinks', CsPhoneLinks, 'a[href^="tel:"]');
9657PluginController.addPlugin('CsExternalLinks', CsExternalLinks, 'a[target="_blank"]');
9658PluginController.addPlugin('CsAccessibilityAnimation', CsAccessibilityAnimation, '.js-toggle-animations');
9659
9660PluginController.addPlugin('CsScrollbar', CsScrollbar);
9661PluginController.addPlugin('CsMainNavigation', CsMainNavigation, '.js-menu-main');
9662PluginController.addPlugin('CsSplide', CsSplide, '.js-splide-slider');
9663PluginController.addPlugin('CsResponsiveVideo', CsResponsiveVideo, '.js-responsive-video');
9664PluginController.addPlugin('CsLightbox', CsLightbox);
9665PluginController.addPlugin('CsScrollToNextFrame', CsScrollToNextFrame, '.js-scroll-to-next-frame');
9666PluginController.addPlugin('CsEdgeNav', CsEdgeNav, '.js-edge-nav');
9667PluginController.addPlugin('CsAccordion', CsAccordion, '.js-accordion');
9668PluginController.addPlugin('CsFlipText', CsFlipText, '.js-flip-text-trigger');
9669PluginController.addPlugin('CsCookieBannerRenderer', CsCookieBannerRenderer, '.js-cscb-edit-box');
9670PluginController.addPlugin('CsCookieBannerHandler', CsCookieBannerHandler, '.js-cscb-edit-box');
9671
9672// core feature must be placed at the bottom of this script
9673document.addEventListener('DOMContentLoaded',
9674  () => PluginController.initializeAllPlugins(), false);
9675
9676export { PluginControlInstance };
9677//# sourceMappingURL=bundle.main.js.map

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.