PageSourceSearch

https://pirha.fi/o/ambientia-pirha-2-theme/js/main.js?browserId=ch…=js&languageId=fi_FI&t=1790263906000

js pirha.fi collected 2026-10-02 10:03:42 UTC 72,128 bytes, 1,866 lines download raw bytes

1/*1790262248000*/
2/**
3 * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Lesser General Public License as published by the Free
7 * Software Foundation; either version 2.1 of the License, or (at your option)
8 * any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13 * details.
14 */
15
16/*
17 * This function gets loaded when all the HTML, not including the portlets, is
18 * loaded.
19 */
20
21
22AUI().ready(function () {
23    // Accordions
24    initPirhaAccordions();
25
26    // close menus when clicking ESC
27    window.addEventListener('keydown', (e) => {
28        if (e.key === 'Escape') {
29            hideMenu("all");
30        }
31    });
32
33    // Hide menus on media breakpoints
34    const mqlDesktop = window.matchMedia("(max-width: 991px)");
35    const mqlDesktopLarge = window.matchMedia("(max-width: 1200px)");
36
37    mqlDesktop.onchange = (e) => {
38        if (!e.matches) {
39            hideMenu('mega');
40        }
41        if (e.matches) {
42            hideMenu('all');
43        }
44    };
45
46    const megaMenuMobile = document.querySelector('#header__mega-menu__mobile');
47    window.onresize = (function(){
48        if(mqlDesktopLarge.matches && megaMenuMobile && megaMenuMobile.style.display != 'none'){
49            setMobileNavigationHeight();
50        }
51    });
52
53    const megaMenu = document.querySelector('#header__mega-menu');
54    document.addEventListener('click', (evt) => {
55        if(megaMenu && !megaMenu.contains(evt.target)) {
56            hideDesktopMegamenu();
57        }
58    });
59
60    /* Mobilenav */
61    mobileNavigation();
62
63    /* Search suggestions */
64    customizeSearchSuggestionsLinks();
65});
66
67/*
68 * This function gets loaded after each and every portlet on the page.
69 *
70 * portletId: the current portlet's id
71 * node: the Alloy Node object of the current portlet
72 */
73Liferay.Portlet.ready(function (_portletId, _node) {
74});
75/*
76 * This function gets loaded when everything, including the portlets, is on
77 * the page.
78 */
79Liferay.on('allPortletsReady', function () {
80    let pirhaDropdowns = [...document.querySelectorAll('.pirha-dropdown')];
81    if (pirhaDropdowns.length > 0) {
82        pirhaDropdowns.forEach(dropdown => {
83            const toggler = dropdown.querySelector('.dropdown-toggle');
84            const content = dropdown.querySelector('.dropdown-menu');
85            if (toggler && content) {
86
87                toggler.addEventListener('click', () => {
88                    toggler.setAttribute('aria-expanded', toggler.getAttribute('aria-expanded') ? 'true' : 'false');
89                    dropdown.classList.toggle('open');
90
91                    setTimeout(() => {
92                        if (dropdown.classList.contains('open')) {
93                            // Move focus on first link or checkbox
94                            const links = [...dropdown.querySelectorAll('a, input[type="checkbox"]')];
95
96                            links[0]?.focus();
97                        }
98                    }, 500);
99                });
100
101                // Close dropdown when clicking outside dropdown
102                document.addEventListener('click', (evt) => {
103                    if(!dropdown.contains(evt.target)) {
104                        toggler.setAttribute('aria-expanded', 'false');
105                        dropdown.classList.remove('open');
106                    }
107                });
108            }
109        });
110    }
111
112    // Mobile search toggler
113    const mobileSearchToggler = document.getElementById('header__top__mobile-search__button');
114    if (mobileSearchToggler) {
115        const mobileSearchContainer = document.getElementById('header__search__mobile');
116        const mobileSearchInput = document.querySelector('#header__search__mobile .search-bar-keywords-input');
117
118        mobileSearchToggler.addEventListener('click', (event) => {
119            mobileSearchToggler.setAttribute('aria-expanded', mobileSearchToggler.getAttribute('aria-expanded') == 'false' ? 'true' : 'false');
120
121            if(mobileSearchInput && document.activeElement != mobileSearchInput){
122                setTimeout(() => {
123                    mobileSearchInput.focus();
124                }, 500);
125            }
126
127            if (mobileSearchContainer) {
128                // check if mobile slide menu should be closed
129                const mobileMenuToggler = document.getElementById('header__top__mobile-menu__button');
130                updateMobileMenuToggler(mobileMenuToggler);
131                mobileSearchContainer.classList.toggle('d-none');
132            }
133        });
134
135        // Hide search element when is clicked outside button or search element
136        document.addEventListener('click', (evt) => {
137            if (mobileSearchToggler.getAttribute('aria-expanded') === 'true') {
138                if(!mobileSearchToggler.contains(evt.target) && !mobileSearchContainer.contains(evt.target)) {
139                    mobileSearchToggler.setAttribute('aria-expanded', 'false');
140                    mobileSearchContainer.classList.add('d-none');
141                }
142            }
143        });
144    }
145
146    /* Open megamenu parent level when lower level page is current page */
147    openActiveSubmenuParents();
148    mainNavArrowKey(document.querySelectorAll('#header__mega-menu__bar .mega-menu__top-level__item'));
149
150    /* Detect and Add external link icons to main and footer link elements */
151    detectExternalLinks('#main-content a');
152    detectExternalLinks('#footer a');
153
154    /* Add uimage viewers */
155    addAUimageViewers();
156});
157
158document.querySelectorAll('.portlet').forEach(function (element) {
159    element.addEventListener("mouseover", function () {
160        const isPortletTopper = element.getElementsByClassName("portlet-topper");
161        if (isPortletTopper) {
162            for (var i = 0; i < isPortletTopper.length; i++) {
163                const isPortletNameText = isPortletTopper[i].getElementsByClassName("portlet-name-text");
164                if (isPortletNameText) {
165                    if (isPortletNameText[0].innerText === "Sisälletyt portletit" || isPortletNameText[0].innerText === "Nested Applications") {
166                        isPortletTopper[i].classList.add("nested-application-topper");
167                    }
168                }
169            }
170        }
171    });
172});
173
174// Hide portlet toppers from Display Template Pages
175if (window.location.href.indexOf("/w/") > -1) {
176    if (document.getElementsByClassName('portlet-topper') !== null) {
177        const elements = document.getElementsByClassName('portlet-topper');
178        for (let i = 0; i < elements.length; i++) {
179            elements[i].style.display = "none";
180        }
181    }
182}
183
184function updateMobileMenuToggler(mobileMenuToggler){
185    if (mobileMenuToggler && mobileMenuToggler.getAttribute('aria-expanded') === 'true') {
186        mobileMenuToggler.setAttribute('aria-expanded', 'false');
187        const icon = mobileMenuToggler.querySelector('.pirha-icon');
188        icon.classList.remove('pirha-icon--close');
189        icon.classList.add('pirha-icon--menu');
190
191        document.getElementById('header__mega-menu__mobile').style.display = 'none';
192        document.querySelector('body').classList.remove('mobile-menu-open');
193    }
194}
195
196// show mega menu by clicking one of the top level buttons
197function showMegaMenuDropdown(index, isMobile = false) {
198    let mobileDropdown = isMobile ? '_mobile': '';
199
200    const clickedDropdown = document.getElementById("mega-menu__dropdown"+ mobileDropdown +"--" + index);
201    const clickedButton = clickedDropdown.previousElementSibling;
202
203    /* Set aria-expanded to false by default on Top-level buttons */
204    document.querySelectorAll('button.mega-menu__top-level__item__content').forEach(button => {
205        if(button !== clickedButton){
206            button.setAttribute('aria-expanded', 'false');
207        }
208    });
209
210    /* on click, toggle aria-expanded attribute */
211    clickedButton.setAttribute('aria-expanded', clickedButton.getAttribute('aria-expanded') == 'false' ? 'true' : 'false');
212    const menuIsExpanded = clickedButton.getAttribute('aria-expanded') == 'true';
213
214    openActiveSubmenuParents();
215    setDropdownHeight(clickedButton);
216
217    // Array of megamenu panels / sections for keyboard pathing
218    const megamenu = [];
219    megamenu.push([...clickedDropdown.querySelectorAll('.level-0')]);
220    megamenu.push([...clickedDropdown.querySelectorAll('.level-1')]);
221    megamenu.push([...clickedDropdown.querySelectorAll('.level-2')]);
222    megamenu.push([...clickedDropdown.querySelectorAll('.mega-menu__bottom-row .journal-content-article > ul')]); // note: keep bottom-row last
223
224    clickedDropdown.removeEventListener('keydown', keyboardPathing, false);
225
226    if( menuIsExpanded){
227        clickedDropdown.addEventListener('keydown', keyboardPathing.bind(null, megamenu), false);
228    }
229
230    /* Prevent navigation mini-menu dropdowns to open outside wrapper */
231    if(clickedDropdown && clickedDropdown.classList.contains('mini-menu__dropdown')) {
232        const offsetLeft = clickedDropdown.getBoundingClientRect().left;
233        const width = clickedDropdown.offsetWidth;
234        const wrapperWidth = document.getElementById('wrapper').offsetWidth;
235        const isEntirelyVisible = (offsetLeft + width <= wrapperWidth);
236
237        if (!isEntirelyVisible) {
238            clickedDropdown.classList.add('position-right');
239        }
240    }
241}
242
243// Navigate mega menu columns
244const keyboardPathing = function(megamenu, e) {
245    var isTabPressed = (e.key === 'Tab' || e.keyCode === 9);
246
247    if (!isTabPressed) {
248        return;
249    }
250
251    const parentMenuID = document.activeElement.closest('ul').getAttribute('data-parent-id');
252    const menuLevel = Number(document.activeElement.closest('ul').getAttribute('data-level'));
253    const lastMegamenuList = megamenu[megamenu.length - 1][0];
254    const hasBottomRowLinks = lastMegamenuList.querySelector('li.mega-menu__dropdown__list__item a') ? true : false;
255
256    /* shift + tab */
257    if(e.shiftKey) {
258        // if 2nd or 3rd column
259        if(menuLevel > 0){
260            // Get open column
261            const activeColumn = megamenu[menuLevel].filter(menu => {
262                return menu.classList.contains('open');
263            })
264
265            // Move focus back to parent column, when active element is first item
266            if(document.activeElement.closest('li') == activeColumn[0].children[0]){
267                document.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentMenuID +'"]').focus();
268                e.preventDefault();
269            }
270        }
271        else if (hasBottomRowLinks){
272            if(document.activeElement.closest('li') == lastMegamenuList.children[0]){
273                const lastLinkOfFirstColumn = megamenu[0][0].querySelector('li.mega-menu__dropdown__list__item:last-child > a');
274                lastLinkOfFirstColumn?.focus();
275                e.preventDefault();
276            }
277        }
278    }
279    /* tab */
280    else {
281        let submenuLevel = menuLevel + 1;
282
283        if(submenuLevel <= 3){
284            let activeSubColumn = megamenu[submenuLevel]?.filter(menu => {
285                return menu.classList.contains('open');
286            })
287
288            const activeColumn = megamenu[menuLevel].filter(menu => {
289                return menu.classList.contains('open');
290            })
291
292            // Move focus to first item of submenu
293            // (navigation column structure is not nested)
294            if(document.activeElement.getAttribute('aria-expanded') == 'true'){
295                activeSubColumn[0].querySelector('a').focus();
296                e.preventDefault();
297            }
298
299            // When current activeElement is not in first column
300            if(menuLevel > 0){
301                // activeELement is Last element in submenu column
302                if(document.activeElement.closest('li') == activeColumn[0].children[activeColumn[0].children.length - 1]){
303                    const nextSibling = document.activeElement.nextElementSibling;
304                    let parentColumnLastChild = document.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentMenuID +'"]').parentNode.lastElementChild;
305                    let returnLevel = parentMenuID
306
307                    if(menuLevel == 2){
308                        parentColumnLastChild = document.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentMenuID +'"]').parentNode.parentNode.lastElementChild;
309                    }
310
311                    const lastChildSubmenuIsOpen = parentColumnLastChild.querySelector('button.toggle-submenu')?.getAttribute('aria-expanded') == 'true';
312
313                    // Return to first column (level-0) when 3rd column is active
314                    // and parent element is last item of 2nd column
315                    if(lastChildSubmenuIsOpen){
316                        returnLevel = document.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentMenuID +'"]').parentNode.parentNode.dataset.parentId;
317                    }
318
319                    const parentNodeEl = document.querySelector('.toggle-submenu[data-target-sub-menu="'+ returnLevel +'"]').parentNode;
320
321                    // If active element doesn't have childs or active element is toggle button
322                    if((document.activeElement.nodeName == 'A' && nextSibling.nodeName != 'BUTTON') || document.activeElement.nodeName == 'BUTTON'){
323
324                        // Check if parent submenu column contains embed web-article links and focus those
325                        if(parentNodeEl.nextElementSibling.classList.contains('mega-menu__link-group')){
326                            const linkGroupLinks = parentNodeEl.nextElementSibling.querySelectorAll('.journal-content-article a');
327
328                            if(linkGroupLinks.length > 0){
329                                linkGroupLinks[0].focus();
330                                e.preventDefault();
331                            }
332                            else{
333                                // last item of parent columns is web-article-link-group, but contains 
333no links
334                                const topLevelParent = document.activeElement.closest('.mega-menu__top-level__item');
335
336                                // focus next main nav's top level link
337                                if(topLevelParent){
338                                    topLevelParent.nextElementSibling.children[0].focus();
339                                    e.preventDefault();
340                                }
341                            }
342                        }
343                        else{
344                            parentNodeEl.nextElementSibling.children[0].focus();
345                            e.preventDefault();
346                        }
347                    }
348                }
349            }
350            // First column (#0)
351            else{
352                const containsEmptyWebArticle = document.activeElement.parentNode.nextElementSibling?.classList.contains('mega-menu__link-group') ? document.activeElement.parentNode.nextElementSibling.children : 0;
353                const webArticleLinks = [...activeColumn[0]?.querySelectorAll('.mega-menu__link-group .journal-content-article a')]; // ember web-article links on column-0
354                const hasSubmenus = document.activeElement.nextElementSibling?.nodeName == 'BUTTON';
355                const toplevelLI = document.activeElement.closest('.mega-menu__top-level__item ');
356
357                //  Embed web-article
358                if(containsEmptyWebArticle.length == 0 && !hasSubmenus){
359                    const currentTopLevelItem = toplevelLI.nextElementSibling?.children[0];
360                    // Return to main nav level if activeElement is last item on column-0
361                    if(currentTopLevelItem){
362                        currentTopLevelItem.focus();
363                        e.preventDefault();
364                    }
365                    else{
366                        document.querySelector('#main-content a').focus();
367                        e.preventDefault();
368                    }
369                }
370                else if(webArticleLinks || hasBottomRowLinks){
371                    const bottomRowLinks = [...lastMegamenuList.querySelectorAll('a')];
372                    const isLastLinkGroupLink = document.activeElement == webArticleLinks[webArticleLinks.length - 1];
373
374                    if((isLastLinkGroupLink && !hasBottomRowLinks)){
375                        const currentTopLevelItem = toplevelLI.nextElementSibling.children[0];
376
377                        if(currentTopLevelItem){
378                            currentTopLevelItem.focus();
379                            e.preventDefault();
380                        }
381                    }
382                    else if(isLastLinkGroupLink && hasBottomRowLinks){
383                        bottomRowLinks[0].focus();
384                        e.preventDefault()
385                    }
386                }
387            }
388        }
389    }
390}
391
392// Show Mobile menu
393function showMobileMegamenu() {
394    const mobileMegamenuToggler = document.getElementById('header__top__mobile-menu__button');
395    const mobileMegamenuTogglericon = mobileMegamenuToggler.querySelector('.pirha-icon');
396    const mobileMegamenu = document.getElementById("header__mega-menu__mobile");
397
398    const documentBody = document.querySelector('body');
399    mobileMegamenuToggler.getAttribute('aria-expanded') == 'true' ? documentBody.classList.remove('mobile-menu-open') : documentBody.classList.add('mobile-menu-open');
400
401    if (mobileMegamenu !== null && mobileMegamenu.style.display !== "none") {
402        mobileMegamenuToggler.setAttribute('aria-expanded','false');
403        mobileMegamenuTogglericon.classList.remove('pirha-icon--close');
404        mobileMegamenuTogglericon.classList.add('pirha-icon--menu');
405
406        hideMegamenu();
407    } else {
408        mobileMegamenuToggler.setAttribute('aria-expanded','true');
409        mobileMegamenuTogglericon.classList.remove('pirha-icon--menu');
410        mobileMegamenuTogglericon.classList.add('pirha-icon--close');
411
412        // showBackDrop("main-content");
413        if (mobileMegamenu !== null) {
414            mobileMegamenu.style.display = "block";
415        }
416
417        setMobileNavigationHeight();
418    }
419
420
421    // Find the active panel item in special panels
422    const activeSpecialPanelItem = document.querySelector('.special-panel .panel-item.active');
423
424    if (activeSpecialPanelItem) {
425        // Find which special panel contains this active item
426        const activeSpecialPanel = activeSpecialPanelItem.closest('.special-panel');
427
428        if (activeSpecialPanel) {
429            const panelId = activeSpecialPanel.getAttribute('data-panel-id');
430            // console.log('Active special panel ID:', panelId);
431
432            // Use existing showPanel function
433            showPanel(panelId, "250", true);
434
435            // // Set up focus management for this panel
436            // manageMobileMenuFocus({ target: activeSpecialPanel, targetCurrentPage: true });
437        }
438    }
439}
440
441function hideMenu(menuToHide) {
442    const mobileMegamenu = document.getElementById("header__mega-menu__mobile");
443    const canHideMegamenu = mobileMegamenu !== null && mobileMegamenu.style.display !== "none";
444
445    if(canHideMegamenu){
446        document.querySelector('body').classList.remove('mobile-menu-open');
447    }
448
449    switch (menuToHide) {
450        case "all":
451            if (canHideMegamenu) hideMegamenu();
452            if (document.querySelector("#header__mega-menu__bar .mega-menu__top-level__item__content[aria-expanded='true']")) {
453                hideDesktopMegamenu();
454            }
455            const mobileMenuToggler = document.querySelector('#header__top__mobile-menu__button');
456            updateMobileMenuToggler(mobileMenuToggler);
457            break;
458        case "mega":
459            if (canHideMegamenu) hideMegamenu();
460    }
461}
462
463function hideMegamenu() {
464    const mobileMegamenu = document.getElementById("header__mega-menu__mobile");
465    mobileMegamenu.style.display = "none";
466}
467
468function hideDesktopMegamenu() {
469    const expandedTopLevelItem = document.querySelector("#header__mega-menu__bar .mega-menu__top-level__item__content[aria-expanded='true']");
470    if(expandedTopLevelItem){
471        expandedTopLevelItem.focus();
472        expandedTopLevelItem.setAttribute('aria-expanded', 'false');
473    }
474}
475
476document.querySelectorAll('.portlet').forEach(function (element) {
477    // Brings down the portlet-header element more down when the user does not have permissions to edit portlets
478    element.addEventListener("mouseover", function () {
479        const isPortletTopper = element.firstElementChild.classList.contains("portlet-topper");
480        if (!isPortletTopper) {
481            const nodeList = element.childNodes;
482            for (let i = 0; i < nodeList.length; i++) {
483                for (let j = 0; j < nodeList[i].childNodes.length; j++) {
484                    if (nodeList[i].childNodes[j].classList !== undefined && nodeList[i].childNodes[j].classList.contains("portlet-header")) {
485                        const portletHeader = element.getElementsByClassName(nodeList[i].childNodes[j].classList)[0];
486                        portletHeader.setAttribute('style', 'top: -1.5rem !important');
487                        break;
488                    }
489                }
490            }
491        }
492    });
493});
494
495
496/* PIRHA 2.0 */
497/* ************** */
498
499// Main navigation - Navigate top level links with arrow keys (left and right)
500function mainNavArrowKey([...topLevelItems]){
501    const firstLink = topLevelItems[0];
502    const lastLink = topLevelItems[topLevelItems.length - 1];
503
504    topLevelItems.forEach(topLevelItem => {
505        topLevelItem.addEventListener('keydown', function(e) {
506            // Left
507            if (e.keyCode == 37) {
508                if(document.activeElement?.parentElement != firstLink){
509                    document.activeElement.parentElement.previousElementSibling?.children[0].focus();
510                }
511                return false;
512            }
513            // Right
514            if (e.keyCode == 39) {
515                if(document.activeElement?.parentElement != lastLink){
516                    document.activeElement.parentElement.nextElementSibling?.children[0].focus();
517                }
518                return false;
519            }
520        })
521    })
522}
523
524
525/* Toggle megamenu's submenus */
526function toggleSubmenu(button){
527    const targetSubmenuId = button.dataset.targetSubMenu;
528    const targetSubmenuLevel = Number(button.dataset.targetLevel) + 1;
529    const isExpanded = button.getAttribute('aria-expanded') == 'true' ? true : false;
530    let submenus;
531
532    // Select megamenu column(s) by targeted submenu level
533    if(targetSubmenuLevel == 1 && isExpanded){
534        submenus = document.querySelectorAll(
535            `.mega-menu__dropdown .submenu-column-${targetSubmenuLevel} ul.open,
536             .mega-menu__dropdown .submenu-column-${targetSubmenuLevel+1} ul.open`);
537    }
538    else if(targetSubmenuLevel == 2){
539        submenus = document.querySelectorAll(
540            `.mega-menu__dropdown .submenu-column-${targetSubmenuLevel} ul`);
541    }
542    else{
543        submenus = document.querySelectorAll(
544            `.mega-menu__dropdown .submenus ul`);
545    }
546    submenus.forEach(submenu => submenu.classList.remove('open'));
547
548    // Get targeted submenu by id
549    const targetedSubmenu = [...submenus].filter( submenu => submenu.dataset.parentId == targetSubmenuId );
550
551    if(!isExpanded){
552        targetedSubmenu[0].classList.add('open')
553    }
554
555    controlAriaAttributes(button, submenus);
556    setDropdownHeight(button);
557}
558
559/* Set correct aria-attributes to megamenu items */
560function controlAriaAttributes(element, submenus = ''){
561    if(element !== null){
562        const ariaExpanded = element.getAttribute('aria-expanded');
563
564        // Reset submenus
565        if(submenus.length > 0){
566            [...submenus].forEach(submenu => {
567                submenu.querySelector('button[aria-expanded="true"]')?.setAttribute('aria-expanded','false');
568
569                const buttonSR = submenu.querySelector('button .sr-text')
570                if(buttonSR) buttonSR.textContent = translate('menu-open')
571            })
572        }
573
574        // If expanded
575        if(ariaExpanded == 'true'){
576            element.setAttribute('aria-expanded', 'false')
577
578            if(element.querySelector('.sr-text') != null){
579                element.querySelector('.sr-text').textContent = translate('menu-open');
580            }
581        }
582        else{
583            // Reset Siblings
584            const allButtons = element.closest('ul').querySelectorAll('li button');
585
586            allButtons.forEach(button => {
587                button.setAttribute('aria-expanded', 'false');
588
589                if(button.querySelector('.sr-text') != null){
590                    button.querySelector('.sr-text').textContent =  translate('menu-open');
591                }
592            });
593
594            // Set clicked
595            element.setAttribute('aria-expanded', 'true')
596
597            if(element.querySelector('.sr-text') != null){
598                element.querySelector('.sr-text').textContent =  translate('menu-close');
599            }
600        }
601    }
602}
603
604/* Set height to megamenu's submenus */
605function setDropdownHeight(clickedButton = null){
606    if(clickedButton){
607        let targetSubmenu = '';
608        let headerHeight = '';
609        let mmContainerHeightCSS = 'height: auto;';
610        let bottomRowHeight = 0;
611
612        // get css variable if it exists
613        const bottomRowCSSVarValue = getComputedStyle(document.documentElement)
614                .getPropertyValue('--mm-bottom-row-height')
615                .trim();
616
617        if(bottomRowCSSVarValue){
618            bottomRowHeight = parseFloat(bottomRowCSSVarValue);
619        }
620
621        const isButtonExpanded = clickedButton.getAttribute('aria-expanded') == 'true';
622        const isTopLevelButton = clickedButton.classList.contains('mega-menu__top-level__item__content');
623        const isTopLevelMinimenuButton = clickedButton.classList.contains('minimenu_button');
624        const isSubmenuToggleButton = clickedButton.classList.contains('toggle-submenu');
625        const containers = document.querySelectorAll('.mega-menu--container');
626
627        if(isTopLevelButton){
628            headerHeight = clickedButton.nextElementSibling.getBoundingClientRect().top;
629            targetSubmenu = clickedButton.nextElementSibling.querySelector('ul.level-0');
630            menuColumnHeight = 0;
631            menuColumnsOpen = clickedButton.nextElementSibling.querySelectorAll('.mega-menu__dropdown__list.open');
632
633            if(menuColumnsOpen){
634                menuColumnsOpen.forEach(column => {
635                    if(menuColumnHeight < column.offsetHeight){
636                        menuColumnHeight = column.offsetHeight;
637                        targetSubmenu = column;
638                    }
639                })
640            }
641
642            if(!isTopLevelMinimenuButton){
643                const currentContainer = clickedButton.nextElementSibling.querySelector('.mega-menu--container');
644                currentSubmenu = currentContainer.querySelector('.mega-menu__dropdown__list');
645                const buttomRow = currentContainer?.querySelector('.mega-menu__bottom-row');
646
647                // set css variable if mega-menu bottowRow exists
648                if(buttomRow){
649                    bottomRowHeight = buttomRow.offsetHeight;
650                    document.documentElement.style.setProperty('--mm-bottom-row-height', bottomRowHeight + 'px');
651                }
652            }
653        }
654        else if(isSubmenuToggleButton){
655            headerHeight = clickedButton.closest('.mega-menu__dropdown').getBoundingClientRect().top;
656            targetSubmenu = document.querySelector(`ul[data-parent-id="${clickedButton.dataset.targetSubMenu}"]`);
657            currentSubmenu = clickedButton.closest('.mega-menu__dropdown__list');
658        }
659
660        let tallestSub = 0;
661        containers.forEach(cont => {
662            if(cont.offsetHeight > 0){
663                cont.querySelectorAll('ul.open').forEach(submenu => {
664                    if(submenu.offsetHeight > tallestSub){
665                        tallestSub = submenu.offsetHeight
666                    }
667                });
668            }
669        });
670
671        // Set heights
672        if(!isTopLevelMinimenuButton){
673            if(tallestSub >= (window.innerHeight - headerHeight)){
674                mmContainerHeightCSS = 'height: '+ Math.round(tallestSub) + 'px;';
675            }
676            else if( isButtonExpanded ){
677                mmContainerHeightCSS = 'height: auto;';
678            }
679
680            targetSubmenu.closest('.mega-menu--container').setAttribute('style', mmContainerHeightCSS +' max-height: calc(100vh - '+ Math.round(headerHeight) +'px);');
681        }
682    }
683}
684
685
686/* Get parent LI elements */
687function getParentLIs(el) {
688    var parents = [];
689    while (el) {
690        if (el.nodeName === 'LI') {
691            parents.push(el);
692        }
693
694        el = el.parentNode;
695    }
696    return parents;
697}
698
699function openActiveSubmenuParents(){
700    const megamenu = document.querySelector('#header__mega-menu');
701
702    if(megamenu){
703        const currentPage = megamenu.querySelector('.mega-menu__dropdown__list__item[aria-current="page"]');
704        const toggleBtns = megamenu.querySelectorAll('.toggle-submenu');
705        const openSubmenus = megamenu.querySelectorAll('ul.open');
706        let parentID = !!currentPage ? currentPage.closest('ul').dataset.parentId : null;
707
708        // Reset menus
709        if(currentPage && !currentPage.closest('ul').classList.contains('open')){
710            toggleBtns.forEach(btn => {
711                btn.setAttribute('aria-expanded', 'false');
712            });
713
714            openSubmenus.forEach(submenu => {
715                submenu.classList.contains('level-0') ? '' : submenu.classList.remove('open');
716            });
717
718            currentPage.focus();
719            currentPage.closest('.mega-menu__top-level__item ').setAttribute('aria-current', 'true');
720            currentPage.closest('ul').classList.add('open');
721        }
722        else{
723            toggleBtns.forEach(btn => {
724                if(!btn.closest('li').hasAttribute('aria-current')){
725                    btn.setAttribute('aria-expanded', 'false');
726                }
727            });
728
729            openSubmenus.forEach(submenu => {
730                submenu.classList.contains('level-0') ? '' : submenu.classList.remove('open');
731            });
732
733            currentPage?.closest('ul').classList.add('open');
734        }
735
736        if(parentID){
737            let parentToggleMenuBtn = megamenu.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentID +'"');
738            let currentMenuLevel = currentPage.closest('ul').dataset.level;
739
740            /* Based on current page menu level, open parent menus, set aria-attributes and sr-texts */
741            while (currentMenuLevel >= 0) {
742                if(!!parentToggleMenuBtn){
743                    parentToggleMenuBtn.setAttribute('aria-expanded', 'true');
744                    parentToggleMenuBtn.closest('li').setAttribute('aria-current', 'true');
745                    parentToggleMenuBtn.querySelector('.sr-text').textContent = translate('menu-close');
746                    parentToggleMenuBtn.closest('ul').classList.add('open');
747
748                    parentID = parentToggleMenuBtn.closest('ul').dataset.parentId;
749                    parentToggleMenuBtn = megamenu.querySelector('.toggle-submenu[data-target-sub-menu="'+ parentID +'"');
750                }
751
752                currentMenuLevel--;
753            }
754        }
755    }
756}
757
758/* External links */
759/* -------------- */
760function isValidUrl(url){
761    try {
762        new URL(url);
763    } catch (e) {
764        return false;
765    }
766
767    return true;
768}
769
770function detectExternalLinks(item) {
771    const locationHostname = window.location.hostname;
772    const links = document.querySelectorAll(item);
773
774    for (let i = 0; i < links.length; i++) {
775        if( isValidUrl(links[i]) ){
776            let domain = new URL(links[i]);
777            let linkHostname = domain.hostname.replace('www.', '');
778
779            if (linkHostname && locationHostname.replace('www.', '') !== linkHostname) {
780                const hasButtonAsChild = links[i].querySelector('[class*="pirha-button"]') ? true : false;
781                const containsClass = /pirha-button/.test(links[i].className)
782
783                if(!hasButtonAsChild && !containsClass){
784                    /* Add external link css class */
785                    addExternalLinkStyling(links[i]);
786                }
787            }
788        }
789    }
790}
791
792/* Add external link css class */
793function addExternalLinkStyling(link) {
794    const UNDESIRABLE_PARENTS = ['.portlet-topper', '.pagination', '.no-external'];
795    let addIcon = true;
796
797    for (let i = 0; i < UNDESIRABLE_PARENTS.length; i++) {
798        if(link.closest(UNDESIRABLE_PARENTS[i]) != null){
799            addIcon = false;
800            break;
801        }
802    }
803
804    if(addIcon){
805        link.classList.add('external-link');
806    }
807}
808
809
810/* Pirha tabs with active tab indicator */
811/* ------------------------------------ */
812function controlTabs(target) {
813    const tabs = document.querySelectorAll(target + ' [role="tab"]');
814    let activeTab = document.querySelector(target + ' .active [role="tab"]');
815    const tabIndicator = document.querySelector(target + ' .tab-indicator');
816
817    if(tabs.length > 0){
818        /* Check tab visibility and set active tab indicator to correct position */
819        if(!!activeTab){
820            activeTab = tabs[0];
821
822            onVisible(activeTab, () => {
823                setTabIndicator(activeTab, tabIndicator);
824            });
825        }
826
827        tabs.forEach((tab) => {
828            tab.addEventListener('click', (e) => {
829                const tabAriaSelected = tab.getAttribute('aria-selected');
830                setTabIndicator(tab, tabIndicator);
831
832                /* Reset filters when changing active tab */
833                document.querySelectorAll(target + ' .tab-content .btn-check').forEach(checkbox => checkbox.checked = false);
834
835                if(tabAriaSelected != 'true'){
836                    // Deactivate other tabs
837                    tabs.forEach(item => {
838                        item.setAttribute('aria-selected', 'false');
839                        item.parentElement.classList.remove('active');
840                    })
841
842                    // Activate clicked tab
843                    tab.setAttribute('aria-selected', 'true');
844                    tab.parentElement.classList.add('active');
845
846                    // Hide other tab panels
847                    document.querySelectorAll(target + ' [role="tabpanel"]').forEach(item => {
848                        item.classList.remove('show', 'active')
849                    })
850
851                    // Show clicked tab panel
852                    const targetPanelEl = document.querySelector('#' + tab.getAttribute('aria-controls'))
853
854                    if(targetPanelEl){
855                        targetPanelEl.classList.add('show', 'active');
856                    }
857                }
858            });
859        })
860    }
861}
862
863function setTabIndicator(activeTab, tabIndicator) {
864    if(!!tabIndicator && !!activeTab){
865        tabIndicator.style.left = activeTab.offsetLeft + 'px';
866        tabIndicator.style.width = activeTab.offsetWidth + 'px';
867    }
868}
869
870// Check when element is visible (not display: none)
871function onVisible(element, callback) {
872    if(element){
873        new IntersectionObserver((entries, observer) => {
874            entries.forEach(entry => {
875                if(entry.intersectionRatio > 0) {
876                    callback(element);
877                    observer.disconnect();
878                }
879            });
880        }).observe(element);
881    }
882}
883
884
885/* Pirha mobile navigation */
886/* ------------------------------------ */
887function setMobileNavigationHeight(){
888    const pageHeader = document.querySelector('#header');
889    const mnavStyleTag = document.querySelector('#mnavHeight');
890
891    if(mnavStyleTag){
892        mnavStyleTag.remove();
893    }
894
895    const visibleHeight = window.innerHeight;
896    const hasMinisiteClass = /cs-/.test(document.body.className);
897    let headerComponentsHeight = 0;
898
899    if(hasMinisiteClass){
900        const headEls = pageHeader.querySelectorAll('.header__content > div');
901
902        headEls.forEach(el => {
903            headerComponentsHeight += el.offsetHeight
904        })
905    }
906    else{
907        headerComponentsHeight = pageHeader.querySelector('.header__content .header__top').offsetHeight
908    }
909
910    const css =
911        `#header__mega-menu__mobile .pirha-mobilenav-container,
912        #header__mega-menu__mobile .menu-list--wrapper,
913        #header__mega-menu__mobile .menu-list--wrapper.current{
914            height: calc(${visibleHeight}px - ${headerComponentsHeight}px - ${pageHeader.offsetTop}px);
915        }`;
916
917    let styleTag = document.createElement('style');
918    styleTag.id = 'mnavHeight';
919    styleTag.appendChild(document.createTextNode(css));
920    document.body.appendChild(styleTag);
921}
922
923// // Show panel for special menus
924function showPanel(panelId, timeout = "250", skipAnimation = false) {
925    const targetPanel = document.querySelector('.panel[data-panel-id="' + panelId + '"]');
926
927    if (!targetPanel) {
928        //console.warn('Panel not found:', panelId);
929        return;
930    }
931
932    if (skipAnimation) {
933        targetPanel.classList.add('active');
934        manageMobileMenuFocus({ target: targetPanel, targetCurrentPage: true  });
935
936    } else {
937        targetPanel.classList.add('slide-in');
938
939        setTimeout(() => {
940            targetPanel.classList.remove('slide-in');
941            manageMobileMenuFocus({ target: targetPanel });
942        }, timeout);
943
944        targetPanel.classList.add('active');
945    }
946}
947
948function mobileNavigation(){
949    const mobileNavigation = document.querySelector('.pirha-mobile-navigation');
950
951    if(mobileNavigation){
952        const openSubmenuButtons = mobileNavigation.querySelectorAll('.mobile-toggle-submenu');
953        const returnButtons = mobileNavigation.querySelectorAll('.return-button');
954        const currentPage = mobileNavigation.querySelector('[aria-current="page"]');
955        const timeout = "300";
956
957        const specialMenuTriggers = mobileNavigation.querySelectorAll('.special-panel .panel-trigger');
958        const specialMenuReturnButtons = mobileNavigation.querySelectorAll('.special-panel .back-button');
959
960
961        if(!!currentPage){
962            const currentMenuSection = currentPage.closest('.menu-list--wrapper');
963            currentPage.classList.add('submenu-visible');
964
965            /* Clear current class */
966            mobileNavigation.querySelector('.current')?.classList.remove('current');
967
968            /* Open submenu of current page if child items exists */
969            !!currentPage.querySelector('.menu-list--wrapper')
970                ? currentPage.querySelector('.menu-list--wrapper').classList.add('current')
971                : currentMenuSection?.classList.add('current');
972
973            if(currentMenuSection){
974                let parentSectionLevel = currentMenuSection.dataset.level - 1;
975                let parentLI = currentMenuSection.closest('li.menu-item');
976                let parentWrapper = parentLI?.closest('.menu-list--wrapper')
977
978                /* Make nested submenu tree visible relative to current page */
979                while (parentSectionLevel > 0) {
980                    parentSectionLevel = parentSectionLevel - 1;
981
982                    parentLI.classList.add('submenu-visible');
983                    parentLI.setAttribute('aria-current', 'true');
984                    parentLI.querySelector('.mobile-toggle-submenu').setAttribute('aria-expanded', 'true');
985                    parentLI = parentWrapper.closest('li.menu-item');
986                    parentWrapper = parentWrapper.closest('[data-level="'+ parentSectionLevel  +'"]');
987                }
988            }
989        }
990
991        /* Slide out SPECIAL submenus */
992        specialMenuReturnButtons.forEach(button => {
993            button.addEventListener('click', () => {
994                const panelId = button.closest('.special-panel').dataset.panelId
995                const parentPanelId = button.closest('.special-panel').dataset.parentPanel
996                const parentPanel = document.querySelector('[data-panel-id="'+ parentPanelId +'"]');
997
998                button.closest('.special-panel').classList.add('slide-out');
999
1000                if(parentPanel){
1001                    parentPanel.classList.add('active');
1002
1003                    setTimeout(() => {
1004                        const parentBtn = parentPanel.querySelector('button[data-target="' + panelId + '"]')
1005                        parentBtn.setAttribute('aria-expanded', 'false');
1006
1007                        button.closest('.special-panel').classList.remove('active', 'slide-out');
1008                    }, timeout);
1009                }
1010
1011                setLastNavigationTrigger(button);
1012                manageMobileMenuFocus({ target: parentPanel, isReturningToParent: true });
1013            })
1014        })
1015
1016        // slide in special
1017        specialMenuTriggers.forEach(button => {
1018            button.addEventListener('click', (e) => {
1019                const targetPanelId = button.dataset.target
1020                button.setAttribute('aria-expanded', 'true');
1021
1022                const parentPanelId = button.closest('.special-panel').dataset.parentPanel
1023                const parentPanel = document.querySelector('[data-panel-id="'+ parentPanelId +'"]');
1024                parentPanel?.classList.add('active');
1025
1026                setLastNavigationTrigger(button);
1027                showPanel(targetPanelId, timeout)
1028            })
1029        })
1030
1031        /* Slide out regular submenus */
1032        returnButtons.forEach(button => {
1033            button.addEventListener('click', () => {
1034                if(button.dataset.levelTarget){
1035                    button.closest('.menu-list--wrapper').classList.add('slide-out');
1036                    button.closest('[data-level="'+ button.dataset.levelTarget +'"]').classList.add('current');
1037
1038                    setTimeout(() => {
1039                        button.closest('li').classList.remove('submenu-visible');
1040                        button.closest('li').querySelector('.mobile-toggle-submenu').setAttribute('aria-expanded', 'false');
1041                        button.closest('.menu-list--wrapper').classList.remove('current', 'slide-out');
1042                    }, timeout);
1043
1044                    const parentWrapper = document.querySelector('.menu-list--wrapper.current[data-level="'+ button.dataset.levelTarget +'"]');
1045
1046                    setLastNavigationTrigger(button);
1047                    manageMobileMenuFocus({ target: parentWrapper, isReturningToParent: true });
1048                }
1049            })
1050        })
1051
1052        /* Slide in regular submenus */
1053        openSubmenuButtons.forEach(button => {
1054            button.addEventListener('click', () => {
1055                button.nextElementSibling.classList.add('slide-in');
1056                button.setAttribute('aria-expanded', 'true');
1057
1058                setTimeout(() => {
1059                    button.closest('.menu-list--wrapper')?.classList.remove('current');
1060                    button.closest('li').classList.add('submenu-visible');
1061                    button.nextElementSibling.classList.remove('slide-in');
1062
1063                    setLastNavigationTrigger(button);
1064                    manageMobileMenuFocus(button.nextElementSibling);
1065                }, timeout);
1066
1067                button.closest('li').classList.add('submenu-visible');
1068                button.nextElementSibling.classList.add('current');
1069            })
1070        })
1071    }
1072}
1073
1074
1075// Helper to store the navigation trigger
1076let lastNavigationTrigger = null;
1077function setLastNavigationTrigger(element) {
1078    lastNavigationTrigger = element;
1079}
1080
1081// Manage focus
1082function manageMobileMenuFocus({ target = null, targetCurrentPage = false, isReturningToParent = false } = {}) {
1083    // Find the current active panel
1084    const currentSection = target ||
1085                          document.querySelector('.pirha-mobilenav-container .menu-list--wrapper.current') ||
1086                          document.querySelector('.pirha-mobilenav-container .special-panel.active');
1087
1088    if (!currentSection) {
1089        return;
1090    }
1091
1092    let elementToFocus = null;
1093
1094    // If returning to parent, focus the element that opened the child panel
1095    if (isReturningToParent && lastNavigationTrigger) {
1096        // main nav specific
1097        if (lastNavigationTrigger.classList.contains('return-button')) {
1098            const parentLi = lastNavigationTrigger.closest('li');
1099            elementToFocus = parentLi?.querySelector('.mobile-toggle-submenu');
1100        }
1101
1102        // Special nav specific
1103        else if (lastNavigationTrigger.classList.contains('back-button')) {
1104            const currentUl = lastNavigationTrigger.closest('ul');
1105            const panelId = currentUl?.getAttribute('data-panel-id');
1106            const parentPanelId = currentUl?.getAttribute('data-parent-panel');
1107
1108            if (panelId && parentPanelId) {
1109                if (parentPanelId === 'root') {
1110                    // returning to root
1111                    const rootPanel = document.querySelector('.menu-list--wrapper.current');
1112                    elementToFocus = rootPanel?.querySelector(':scope > ul > li > .panel-trigger');
1113                } else {
1114                    // Find button with data-target matching the panel ID
1115                    const specialPanel = document.querySelector('.special-panel[data-panel-id="' + parentPanelId + '"]')
1116                                        || document.querySelector('div[data-panel-id="' + parentPanelId + '"] > ul > .special-nav-entry');
1117                    elementToFocus = specialPanel?.querySelector(`button[data-target="${panelId}"]`);
1118                }
1119            }
1120        }
1121
1122        // Fallback: if we couldn't find the specific element, try to use the trigger itself
1123        if (!elementToFocus && currentSection.contains(lastNavigationTrigger)) {
1124            elementToFocus = lastNavigationTrigger;
1125        }
1126
1127        // Clear after use
1128        lastNavigationTrigger = null;
1129    }
1130
1131    // If targetCurrentPage is true, focus the current page item
1132    if (targetCurrentPage) {
1133        elementToFocus = currentSection.querySelector('.menu-item[aria-current="page"] .item') ||
1134                        currentSection.querySelector('.panel-item.active a');
1135    }
1136
1137    // Focus the parent page item (item with aria-current="true")
1138    if (!elementToFocus) {
1139        elementToFocus = currentSection.querySelector('.menu-item[aria-current="true"] .item');
1140
1141        // For special panels, check if there's a parent panel reference
1142        if (!elementToFocus && currentSection.classList.contains('special-panel')) {
1143            const parentPanelId = currentSection.getAttribute('data-parent-panel');
1144            if (parentPanelId) {
1145                // Find the item in the parent panel that links to this panel
1146                const parentPanel = document.querySelector(`.mobile-navigation [data-panel-id="${parentPanelId}"]`);
1147                elementToFocus = parentPanel?.querySelector('.panel-item.active a');
1148            }
1149        }
1150    }
1151
1152    // Focus the first focusable item in the panel
1153    if (!elementToFocus) {
1154        elementToFocus = currentSection.querySelector('.menu-item .item') ||
1155                        currentSection.querySelector('.panel-item a') ||
1156                        currentSection.querySelector('.menu-item--return button') ||
1157                        currentSection.querySelector('a[href], button');
1158    }
1159
1160    // Focus the element if found
1161    if (elementToFocus) {
1162        elementToFocus.focus();
1163    }
1164
1165    // Set up focus trap for this panel
1166    mobilenavTrapFocus(currentSection);
1167}
1168
1169/* Focus trap */
1170// Store the current trap handler so we can remove it later
1171let currentTrapHandler = null;
1172
1173function mobilenavTrapFocus(element) {
1174    const mobilenavContainer = document.querySelector('.pirha-mobilenav-container');
1175
1176    if (!mobilenavContainer) {  return; }
1177    if (!element) { return; }
1178
1179    const eServiceButton = document.querySelector('.pirha-mobilenav-container .button-e-service');
1180    const mobileClose = document.querySelector('.pirha-mobilenav-container .mobile-nav-close');
1181
1182    if ((element.classList.contains('menu-list--wrapper') && element.classList.contains('current')) ||
1183        (element.classList.contains('special-panel') && element.classList.contains('active'))) {
1184        wrapper = element;
1185    } else {
1186        wrapper = element.closest('.menu-list--wrapper.current, .special-panel.active');
1187    }
1188
1189    if (!wrapper) {
1190        wrapper = document.querySelector('.menu-list--wrapper.current, .special-panel.active');
1191    }
1192
1193    let focusableEls;
1194
1195    if (wrapper?.classList.contains('menu-list--wrapper')) {
1196       // For nested structure: get specific elements without traversing into nested panels
1197        const selectors = [
1198            // Return button
1199            ':scope > .menu-item--return button',
1200            ':scope > .menu-item--return a[href]',
1201
1202            // Main menu items, direct children only
1203            ':scope > ul > li > a[href]',
1204            ':scope > ul > li > button',
1205
1206            // Topbar links
1207            ':scope > .topbar-links--mobile a[href]',
1208            ':scope > .topbar-links--mobile button',
1209
1210            // Language selection
1211            ':scope > .language-selection--mobile a[href]',
1212            ':scope > .language-selection--mobile button',
1213
1214            // Mega menu bottom row
1215            ':scope > .mega-menu__bottom-row a[href]',
1216            ':scope > .mega-menu__bottom-row button'
1217        ];
1218
1219        focusableEls = wrapper.querySelectorAll(selectors.join(', '));
1220
1221        // If this is the root panel, filter out items from .special-panel elements
1222        if (wrapper.dataset.panelId === 'root') {
1223            focusableEls = Array.from(focusableEls).filter(el => {
1224                return !el.closest('ul.special-panel');
1225            });
1226        }
1227
1228    } else if (wrapper?.classList.contains('special-panel')) {
1229        focusableEls = wrapper.querySelectorAll('a[href], button');
1230    }
1231
1232    let filteredElements =  [...(focusableEls || [])];
1233
1234    // Add out of current panel/section links
1235    if(mobileClose !== null && eServiceButton !== null){
1236        filteredElements = [mobileClose, eServiceButton, ...filteredElements];
1237    }
1238
1239    if (filteredElements.length === 0) {
1240        return;
1241    }
1242
1243    // For screen reader users on touch screen devices
1244    // tabindex to all other than visible items
1245    // reduces the possibility to focus on "hidden" elements outside active menu section
1246    const allFocusableNavItems = mobilenavContainer.querySelectorAll('a[href]:not([disabled]), button:not([disabled])');
1247    allFocusableNavItems.forEach(element => {
1248        element.removeAttribute('tabindex');
1249    });
1250
1251    wrapper.setAttribute('tabindex', '-1');
1252
1253    const elementsToHide = [...allFocusableNavItems].filter(item => !filteredElements.includes(item));
1254    elementsToHide.forEach(element => {
1255        element.setAttribute('tabindex', '-1');
1256    });
1257
1258    // Keyboard navigation with Tab and Tab+shift keys
1259    var firstFocusableEl = filteredElements[0];
1260    var lastFocusableEl = filteredElements[filteredElements.length - 1];
1261    var KEYCODE_TAB = 9;
1262
1263    // Remove previous event listener if it exists
1264    if (currentTrapHandler) {
1265        mobilenavContainer.removeEventListener('keydown', currentTrapHandler);
1266    }
1267
1268    // Create new handler
1269    currentTrapHandler = function(e) {
1270        var isTabPressed = (e.key === 'Tab' || e.keyCode === KEYCODE_TAB);
1271
1272        if (!isTabPressed) {
1273            return;
1274        }
1275
1276        /* shift + tab */
1277        if(e.shiftKey) {
1278            if (document.activeElement === firstFocusableEl) {
1279                lastFocusableEl.focus();
1280                e.preventDefault();
1281            }
1282        }
1283        /* tab */
1284        else {
1285            if (document.activeElement === lastFocusableEl) {
1286                firstFocusableEl.focus();
1287                e.preventDefault();
1288            }
1289        }
1290    };
1291
1292    // Add new event listener
1293    mobilenavContainer.addEventListener('keydown', currentTrapHandler);
1294}
1295
1296
1297// Pirha accordions
1298function initPirhaAccordions() {
1299    const accordions = document.querySelectorAll('.pirha-accordions');
1300    if (accordions) {
1301        accordions.forEach(accordion => {
1302            accordionActions(accordion);
1303            accordionPaginator(accordion);
1304            accordionToggleAll(accordion);
1305        })
1306
1307        openAccordionBasedOnUrlHash();
1308    }
1309}
1310
1311function accordionActions(accordion) {
1312    const buttons = accordion.querySelectorAll('.pirha-accordion__toggler');
1313
1314    if (buttons) {
1315        buttons.forEach(button => {
1316            const content = button.parentElement.nextElementSibling;
1317            const panel = button.closest('.pirha-accordion__item');
1318
1319            button.addEventListener('click', () => {
1320                button.setAttribute('aria-expanded', button.getAttribute('aria-expanded') === 'true' ? 'false' : 'true');
1321
1322                if (content && panel) {
1323                    if ( button.getAttribute('aria-expanded') === 'true') {
1324                        panel.classList.add('pirha-accordion__item--open');
1325                        content.classList.remove('d-none');
1326                    } else {
1327                        panel.classList.remove('pirha-accordion__item--open');
1328                        content.classList.add('d-none');
1329                    }
1330                }
1331
1332                updateUrlHash(button);
1333            });
1334        });
1335    }
1336}
1337
1338function updateUrlHash(clickedElement) {
1339    const accordionId = clickedElement.querySelector('span[id]')?.id;
1340
1341    if (accordionId && clickedElement.getAttribute('aria-expanded') === 'true') {
1342        history.replaceState(null, null, `#${accordionId}`);
1343    }
1344}
1345
1346function openAccordionBasedOnUrlHash() {
1347    const hash = window.location.hash?.split('?')[0];
1348
1349    if (hash) {
1350        const targetElement = document.querySelector(hash);
1351
1352        if (targetElement) {
1353            const accordionButton = targetElement.closest('.pirha-accordion__toggler');
1354
1355            if (accordionButton && accordionButton.getAttribute('aria-expanded') === 'false') {
1356                accordionButton.click();
1357            }
1358
1359            setTimeout(() => {
1360                targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
1361            }, 150);
1362        }
1363    }
1364}
1365
1366
1367function accordionPaginator(accordion) {
1368    const button = accordion.querySelector('.pirha-accordions__button--paginator');
1369    const panels = [...accordion.querySelectorAll('.pirha-accordion__item')]
1370
1371    if (button && panels.length > 0) {
1372        const icon = button.querySelector('.pirha-icon');
1373        const button_content = button.querySelector('.pirha-button__content')
1374        const panels_total = parseFloat(button.dataset.panels);
1375        const panels_visible = parseFloat(button.dataset.panelsShow);
1376        let allVisible = false;
1377
1378        if (panels_total && panels_visible) {
1379            button.addEventListener('click', () => {
1380                const firstHidden = panels.findIndex(item => item.classList.contains('d-none'));
1381                const lastVisible = firstHidden + panels_visible;
1382                const focusTarget = allVisible ? lastVisible : firstHidden;
1383
1384                if (!allVisible) {
1385                    // Change visibility of panels
1386                    for (let i = 0; i < panels.length; i++) {
1387                        if (i < lastVisible) panels[i].classList.remove('d-none');
1388                    }
1389                } else {
1390                    // Reset to start state
1391                    for (let i = 0; i < panels.length; i++) {
1392                        if (i > lastVisible) panels[i].classList.add('d-none');
1393                    }
1394                }
1395
1396                // Hide button if there is no more panels hidden
1397                if (!panels.find(item => item.classList.contains('d-none'))) {
1398                    button_content.innerHTML = translate('accordion-paginator-show-less');
1399                    icon.classList.remove('pirha-icon--chevron-down');
1400                    icon.classList.add('pirha-icon--chevron-up');
1401
1402                    allVisible = true;
1403                } else {
1404                    button_content.innerHTML = translate('accordion-paginator-show-more');
1405                    // Change icon back to chevron down
1406                    if (icon.classList.contains('pirha-icon--chevron-up')) {
1407                        icon.classList.remove('pirha-icon--chevron-up');
1408                        icon.classList.add('pirha-icon--chevron-down');
1409                    }
1410
1411                    allVisible = false;
1412                }
1413
1414                // Move focus to the first panel that was set visible
1415                setTimeout(function(){
1416                    const focusable = panels[focusTarget].querySelector('.pirha-accordion__toggler');
1417                    if (focusable) focusable.focus();
1418                },200);
1419            });
1420        }
1421    }
1422};
1423
1424function accordionToggleAll(accordion) {
1425    const button = accordion.querySelector('.pirha-accordions__toggle--all');
1426    const panels = [...accordion.querySelectorAll('.pirha-accordion__item')];
1427
1428    if (button && panels.length > 0) {
1429        let isOpen = false;
1430        button.addEventListener('click', () => {
1431            panels.forEach(panel => {
1432
1433                const toggler = panel.querySelector('.pirha-accordion__toggler');
1434                const content = panel.querySelector('.pirha-accordion__body');
1435
1436                if (isOpen) {
1437                    toggler.setAttribute('aria-expanded','false');
1438                    content.classList.add('d-none');
1439                    panel.classList.remove('pirha-accordion__item--open');
1440                } else {
1441                    toggler.setAttribute('aria-expanded','true');
1442                    content.classList.remove('d-none');
1443                    panel.classList.add('pirha-accordion__item--open')
1444                }
1445            });
1446
1447            if (!isOpen) {
1448                accordion.classList.add('pirha-accordions__open--all');
1449                button.innerHTML = translate('accordion-close-all');
1450                isOpen = true;
1451            } else {
1452                accordion.classList.remove('pirha-accordions__open--all');
1453                button.innerHTML = translate('accordion-open-all');
1454                isOpen = false;
1455            }
1456
1457        });
1458    }
1459}
1460
1461function translate(word) {
1462    if (word === "") return;
1463    let lang = "fi";
1464    const i18n = {
1465        "fi": {
1466            "accordion-open-all": "Avaa kaikki",
1467            "accordion-close-all": "Sulje kaikki",
1468            "accordion-paginator-show-more": "Näytä lisää",
1469            "accordion-paginator-show-less": "Näytä vähemmän",
1470            "menu-open": "Avaa valikko",
1471            "menu-close": "Sulje valikko"
1472        },
1473        "en": {
1474            "accordion-open-all": "Show all",
1475            "accordion-close-all": "Close all",
1476            "accordion-paginator-show-more": "Show more",
1477            "accordion-paginator-show-less": "Show less",
1478            "menu-open": "Open menu",
1479            "menu-close": "Close menu"
1480        }
1481    }
1482    const langFull = Liferay.ThemeDisplay.getLanguageId();
1483
1484    if (langFull.split("_")[0] !== 'fi') {
1485        lang = 'en'; //langFull.split("-")[0];
1486    }
1487
1488    return i18n[lang][word];
1489}
1490
1491
1492// Pirha 2.0 Create Anchor links
1493// for structure "Ankkurilinkit"
1494// -------------------------------------------------------------------------------------------
1495
1496function getAnchorLists() {
1497    const anchorLists = [...document.querySelectorAll('.pirha-anchorlist')];
1498
1499    if (anchorLists.length > 0) {
1500        anchorLists.forEach(list => initAnchorLists(list));
1501    }
1502}
1503
1504function initAnchorLists(list) {
1505    const layoutColumn = list.closest('#main-content').querySelector("#column-3");
1506    const anchorlistContainer = list.querySelector('.pirha-anchorlist__content');
1507
1508    if (anchorlistContainer) {
1509        const selectedDatatypesArr = anchorlistContainer.dataset.content.split(',');
1510
1511        if (selectedDatatypesArr.length > 0) {
1512            const targetHeadings = 'h1, h2:not(.portlet-title-text), h3'
1513            const targetElements = layoutColumn.querySelectorAll(targetHeadings)
1514            const filteredData = filterElementsByType(targetElements, selectedDatatypesArr)
1515
1516            createAnchors(filteredData, anchorlistContainer)
1517        }
1518    }
1519}
1520
1521function createAnchors(elements, container) {
1522    if(elements){
1523        // Create a map to track anchor IDs
1524        const urlCountMap = new Map();
1525
1526        // Process each object in the array
1527        const anchorData = elements.map(element => {
1528            const anchorText = element.innerText.trim();
1529            const anchorId = anchorText.toLowerCase().replace(/ä|å/g,"a").replace(/ö/g,"o").replace(/[^\w\s]/gi, "").replace(/ /g,"-");
1530            const count = urlCountMap.get(anchorId) || 0;
1531
1532            urlCountMap.set(anchorId, count + 1);
1533
1534            // Rename the anchor id if it's a duplicate
1535            const uniqueId = count > 0 ? `${anchorId}-${count}` : anchorId;
1536
1537            element.setAttribute('id', uniqueId)
1538
1539            return { url: uniqueId, text: anchorText };
1540        });
1541
1542        if (container.dataset.manual == 'false'){
1543            createAnchorlinkList(anchorData, container)
1544        }
1545    }
1546}
1547
1548function createAnchorlinkList(data, container){
1549    if(data && container){
1550        const ul = container.querySelector('ul')
1551
1552        // Change order if selected Alphabetical listing
1553        if (container.dataset.order !== "undefined" && container.dataset.order == 'alphabetical') data.sort((a,b) => a.text.localeCompare(b.text));
1554
1555        // Create link
1556        data.forEach(link => {
1557            let li = document.createElement('li');
1558            let a = document.createElement('a');
1559            a.href = "#" + link.url;
1560            a.innerText = link.text;
1561            li.appendChild(a);
1562            ul.appendChild(li);
1563        });
1564    }
1565}
1566
1567function filterElementsByType(targetElements, selectedDatatypesArr){
1568    const unDesiredParents = ['.pirha-anchorlist', '.pirha-accordion__body', '.d-none', '.cta-lifts']
1569    // exception is a child element of ".journal-content-article" that would otherwise be filtered out when type: "webcontent" is unselected
1570    const childClassExceptions = ['.pirha-accordions']
1571
1572    let filtered = Array.from(targetElements);
1573    filtered = filtered.filter(element => !element.closest(unDesiredParents))
1574
1575    let parentCSSselectors = [];
1576    selectedDatatypesArr.forEach(type => {
1577        switch (type) {
1578            case "webcontent":
1579                parentCSSselectors.push(".journal-content-article");
1580                break;
1581            case "accordion":
1582                parentCSSselectors.push(".pirha-accordions");
1583                break;
1584            case "ptv":
1585                parentCSSselectors.push("div[class*='_ptv_widget']");
1586        }
1587    })
1588
1589    if(parentCSSselectors.length > 0){
1590        // Filter out elements with unselected types and include exceptions if an exception is a child element of a parent selector
1591        return filtered.filter(element => {
1592            // Allow accordion + journal-article combination if journal-article is not selected
1593            const childException = !element.closest(parentCSSselectors.join())?.classList.contains(childClassExceptions.join())
1594                            && element.closest(parentCSSselectors.join())?.querySelector(childClassExceptions.join()) ? true : false;
1595
1596            const isJournalArticleH1 = element.nodeName === 'H1'
1597                                    && element.closest('.journal-content-article')
1598                                    && !element.closest('.journal-content-article').querySelector(childClassExceptions.join()) ? true : false;
1599
1600            if(isJournalArticleH1){
1601                return !element
1602            }
1603
1604            if(childException){
1605                return !element.closest(parentCSSselectors.join())
1606            }
1607            else{
1608                return element.closest(parentCSSselectors.join())
1609            }
1610        });
1611    }
1612}
1613
1614
1615/*
1616 Alloy image viewer from Intranet
1617 */
1618function addAUimageViewers() {
1619    const myGalleryDivs = document.querySelectorAll('div.intra-aui-image-viewer-container, div.intra-aui-image-viewer-gallery');
1620    const firstChildDivs = [];
1621    myGalleryDivs.forEach(div => {
1622        const firstChildDiv = div.querySelector('div');
1623        if (firstChildDiv) {
1624            firstChildDivs.push(firstChildDiv);
1625        }
1626    });
1627
1628    firstChildDivs.forEach(div => {
1629        let captionP = div.querySelector(`.intra-alloy-caption`);
1630        let caption = "";
1631        if (captionP) {
1632            caption = captionP.textContent.trim();
1633            if (caption === "") {
1634                captionP.remove();
1635            }
1636        }
1637        let showThumbnails = div.querySelectorAll('a').length > 1;
1638        YUI().use('aui-image-viewer', function (Y) {
1639            let imageViewer = new Y.ImageViewer({
1640                caption: caption,
1641                centered: true,
1642                imageAnim: {
1643                    duration: 0.5,
1644                    easing: 'easeIn'
1645                },
1646                links: '#' + div.id + ' a',
1647                showInfo: false,
1648                showPlayer: false,
1649                zIndex: 999,
1650                infoTemplate: '',
1651                thumbnailsConfig: showThumbnails,
1652            }).render();
1653            let backdrop = document.querySelector('.yui3-widget-bd');
1654            let imageElement = document.querySelector('.image-viewer-base-image');
1655            function stopPropagationHandler(event) {
1656                event.stopPropagation();
1657            }
1658
1659            imageElement.addEventListener('click', stopPropagationHandler);
1660            backdrop.addEventListener('click', function () {
1661                imageViewer.hide();
1662            });
1663        })
1664    })
1665
1666}
1667
1668/* Begin lifts to equal height script */
1669const changeToEqualHeight = () => {
1670    let layoutRows = Array.from(document.querySelectorAll('.portlet-layout.row'));
1671
1672    layoutRows.forEach((row, index) => {
1673        const nestedPortlets = row.querySelectorAll('.portlet-nested-portlets');
1674        const hasPirhaSidebar = row.querySelector('.pirha-sidebar') ? true : false;
1675
1676        /* if row contains nested-portlets */
1677        if(nestedPortlets.length > 0){
1678            nestedPortlets.forEach(nestedPortlet => {
1679                const nestedLayoutRows = nestedPortlet.querySelectorAll('.portlet-layout');
1680
1681                nestedLayoutRows.forEach(nlRow => {
1682                    const nestedLifts = nlRow.querySelectorAll('.pirha-lift');
1683                    liftsToEqualHeight(nestedLifts);
1684                });
1685            })
1686        }
1687
1688        /* Lift rows outside nested-portlets */
1689        else{
1690            let lifts = row.querySelectorAll('.pirha-aihepiiri .pirha-lift');
1691
1692            if (lifts.length > 0) {
1693                if(hasPirhaSidebar){
1694                    lifts = [...lifts].filter(lift => lift.closest('.portlet-nested-portlets'))
1695                }
1696                liftsToEqualHeight(lifts);
1697            }
1698        }
1699    })
1700}
1701
1702//Changing height is not needed if the row has only one lift.
1703const liftsToEqualHeight = (lifts) => {
1704    let maxHeight = 0;
1705    const mqlTablet = window.matchMedia("(max-width: 767px)");
1706
1707    if(!mqlTablet.matches){
1708        lifts.forEach(lift => {
1709            lift.style.minHeight = 'auto';
1710            const liftHeight = lift.getBoundingClientRect().height
1711            maxHeight = Math.max(maxHeight, liftHeight);
1712        })
1713    }
1714
1715    lifts.forEach(lift => {
1716        lift.style.minHeight = maxHeight + 'px';
1717    })
1718}
1719
1720//If there's lifts then keep them same height.
1721if(document.querySelectorAll('.pirha-lift').length > 1){
1722    let timeId = null;
1723
1724    const resize = () => {
1725        clearTimeout(timeId);
1726        timeId = setTimeout(changeToEqualHeight, 100);
1727    }
1728
1729    window.addEventListener('load', resize)
1730    window.addEventListener("resize", resize)
1731}
1732/* /End lifts to equal height script */
1733
1734/*
1735* Serivce-chain
1736* set rows height equal
1737*/
1738function initServiceChain(){
1739    const serviceChains = document.querySelectorAll('.service-chain')
1740
1741    serviceChains.forEach(serviceChain => {
1742        const columns = serviceChain.querySelectorAll('.service-column')
1743        let columnEls = []
1744
1745        // Collect targeted rows into columnEls array
1746        columns.forEach(column => {
1747            const rows = column.querySelectorAll('.bottom-section--heading, .bottom-section-links li')
1748            let rowEls = []
1749
1750            rows.forEach(row => {
1751                row.removeAttribute('style')
1752                rowEls.push(row)
1753            });
1754
1755            columnEls.push(rowEls)
1756        });
1757
1758        // Fill arrays with null to make them equal length
1759        const maxLength = Math.max(...columnEls.map(arr => arr.length));
1760        const equalLengthArrays = columnEls.map(arr => {
1761            const zerosToAdd = maxLength - arr.length;
1762            return arr.concat(new Array(zerosToAdd).fill(null));
1763        });
1764
1765        // Get max row height between each columns
1766        const maxRowHeightValue = equalLengthArrays[0]?.map(function(_, columnIndex) {
1767            return Math.max(...equalLengthArrays.map(row => {
1768                return row[columnIndex] !== null ? row[columnIndex].getBoundingClientRect().height : 0
1769            }));
1770        });
1771
1772        // Set li -elements to equal height on a matching row
1773        if(maxRowHeightValue){
1774            columnEls.forEach((col) => {
1775                col.forEach((row, index) => {
1776                    if(row.getBoundingClientRect().height != maxRowHeightValue[index]){
1777                        row.style.minHeight = maxRowHeightValue[index] +'px'
1778                    }
1779                })
1780            })
1781        }
1782    });
1783}
1784
1785if(document.querySelectorAll('.service-chain').length > 0){
1786    let timeId = null;
1787    const mqlTablet = window.matchMedia("(max-width: 575px)");
1788
1789    const resize = () => {
1790        if(!mqlTablet.matches){
1791            clearTimeout(timeId);
1792            timeId = setTimeout(initServiceChain, 300);
1793        }
1794    }
1795
1796    window.addEventListener('load', resize)
1797    window.addEventListener("resize", resize)
1798}
1799/* /End of Serivce-chain */
1800
1801function customizeSearchSuggestionsLinks() {
1802
1803    const TARGET_CLASS = 'search-bar-suggestions-results-list';
1804    const REMOVE_STRING = '/web/guest';
1805
1806    let activeListElement = null;
1807    let listObserver = null;
1808
1809    const cleanLinks = (container) => {
1810        const links = container.querySelectorAll('a[href]');
1811
1812        links.forEach(link => {
1813            const href = link.getAttribute('href');
1814            if (href && href.includes(REMOVE_STRING)) {
1815                link.setAttribute('href', href.replace(REMOVE_STRING, ''));
1816            }
1817        });
1818    };
1819
1820    const startObservingList = (listElement) => {
1821        if (listObserver) {
1822            listObserver.disconnect();
1823        }
1824
1825        activeListElement = listElement;
1826
1827        cleanLinks(listElement);
1828
1829        listObserver = new MutationObserver(() => {
1830            cleanLinks(listElement);
1831        });
1832
1833        listObserver.observe(listElement, {
1834            childList: true,
1835            subtree: true,
1836            attributes: true
1837        });
1838    };
1839
1840    const stopObservingList = () => {
1841        if (listObserver) {
1842            listObserver.disconnect();
1843            listObserver = null;
1844        }
1845        activeListElement = null;
1846    };
1847
1848    const documentObserver = new MutationObserver(() => {
1849        const currentList = document.querySelector(`.${TARGET_CLASS}`);
1850
1851        if (!activeListElement && currentList) {
1852            startObservingList(currentList);
1853            return;
1854        }
1855
1856        if (activeListElement && !document.contains(activeListElement)) {
1857            stopObservingList();
1858        }
1859    });
1860
1861    documentObserver.observe(document.documentElement, {
1862        childList: true,
1863        subtree: true
1864    });
1865
1866}

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.