document.addEventListener('DOMContentLoaded', function() { /** * =================================================================== * 1. OSNOVNA FUNKCIONALNOST MOBILNE NAVIGACIJE * =================================================================== */ // === MOBILNI MENI: Preklop vidnosti menija === try { const icon = document.querySelector('.mobile-menu-icon'); const root = document.documentElement; if (!icon) { console.error('Mobile menu icon not found.'); } else { icon.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); root.classList.toggle('nav-open'); }); document.addEventListener('click', (e) => { if (root.classList.contains('nav-open') && !e.target.closest('.mobile-nav-panel') && !e.target.closest('.mobile-menu-icon')) { root.classList.remove('nav-open'); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { root.classList.remove('nav-open'); } }); } } catch (err) { console.error('Error in Mobile Menu Toggle:', err); } // --- Mobilni Accordion podmeni --- try { const mobileNav = document.querySelector('.mobile-nav-panel .mobile-nav'); if (mobileNav) { const menuItemsWithChildren = mobileNav.querySelectorAll('.menu-item-has-children'); menuItemsWithChildren.forEach(item => { const link = item.querySelector('a'); if (link) { link.addEventListener('click', (event) => { if (event.currentTarget.parentElement.classList.contains('menu-item-has-children')) { event.preventDefault(); } const parentLi = event.currentTarget.parentElement; menuItemsWithChildren.forEach(otherItem => { if (otherItem !== parentLi && otherItem.classList.contains('open')) { otherItem.classList.remove('open'); } }); parentLi.classList.toggle('open'); }); } }); } } catch (error) { console.error('Error in Mobile Accordion Menu:', error); } // Desktop: top-level dropdown parents (Who We Are, What We Do, For Students) are // triggers, not real pages. Their href="#" caused a dead "/en/#" navigation on click. // The submenu already opens on hover, so just cancel the navigation. try { document.querySelectorAll('.desktop-nav .menu-item-has-children > a').forEach((a) => { a.addEventListener('click', (e) => { e.preventDefault(); }); }); } catch (error) { console.error('Error disabling desktop dropdown parent links:', error); } /** * =================================================================== * 2. IZBOLJŠAVE UPORABNIŠKE IZKUŠNJE (UX/UI) * =================================================================== */ // --- Funkcija za poudarjanje aktivne povezave v navigaciji --- function highlightActiveLink() { try { const currentPath = window.location.pathname; const navLinks = document.querySelectorAll('.navigation a'); let bestMatch = null; let longestMatch = 0; navLinks.forEach(link => { if (link.closest('.dropdown-menu')) return; const linkPath = new URL(link.href, window.location.origin).pathname; if (currentPath.startsWith(linkPath) && linkPath.length > longestMatch) { longestMatch = linkPath.length; bestMatch = link; } }); if (bestMatch) { const parentMenuItem = bestMatch.closest('.menu-item'); if (parentMenuItem) { parentMenuItem.classList.add('active-page'); } } } catch (error) { console.error("Error highlighting active link:", error); } } // --- Funkcionalnost za preklopnik jezika (Desktop Dropdown + Mobile Modal) --- function setupLanguageSwitcher() { try { const path = window.location.pathname; const pathParts = path.split('/').filter(Boolean); const validLangs = ['en', 'si', 'mk']; let pathWithoutLang = '/'; if (pathParts.length > 0 && validLangs.includes(pathParts[0])) { pathWithoutLang = '/' + pathParts.slice(1).join('/'); if (path.endsWith('/') && pathWithoutLang !== '/') { pathWithoutLang += '/'; } } else { pathWithoutLang = path; } let docLang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase(); if (docLang === 'sl') docLang = 'si'; // (correct ISO for Slovene) maps to the site's /si/ locale document.querySelectorAll('a[data-lang]').forEach(link => { const targetLang = link.getAttribute('data-lang'); const newUrl = `/${targetLang}${pathWithoutLang}`; link.setAttribute('href', newUrl.replace(/\/{2,}/g, '/')); link.classList.toggle('is-current', targetLang === docLang); // Manually choosing a language is a deliberate preference — don't nag with the banner afterwards. link.addEventListener('click', () => { try { localStorage.setItem('msos-lang-banner', 'dismissed'); } catch (e) {} }); }); const desktopSelectors = document.querySelectorAll('.header-right-wrapper .language-selector'); desktopSelectors.forEach(selector => { const currentLang = selector.querySelector('.current-lang'); if (currentLang) { currentLang.addEventListener('click', (event) => { event.stopPropagation(); const isActive = selector.classList.contains('active'); document.querySelectorAll('.language-selector.active').forEach(s => s.classList.remove('active')); if (!isActive) { selector.classList.add('active'); } }); } }); window.addEventListener('click', () => { desktopSelectors.forEach(s => s.classList.remove('active')); }); const mobileLangButton = document.getElementById('mobile-lang-trigger'); const languageModal = document.getElementById('language-modal-overlay'); const closeModalButton = document.getElementById('language-modal-close'); if (mobileLangButton && languageModal && closeModalButton) { mobileLangButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); languageModal.classList.add('active'); }); const closeModal = () => { languageModal.classList.remove('active'); }; closeModalButton.addEventListener('click', closeModal); languageModal.addEventListener('click', (e) => { if (e.target === languageModal) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && languageModal.classList.contains('active')) closeModal(); }); } } catch (error) { console.error('Error in Language Switcher setup:', error); } } /** * =================================================================== * 3. OSTALE FUNKCIONALNOSTI (FAQ, ACTIVITIES, ITD.) * =================================================================== */ // --- FAQ Accordion --- try { document.querySelectorAll('.faq-item h3').forEach(header => { header.addEventListener('click', () => { const faqItem = header.parentElement; faqItem.classList.toggle('open'); }); }); } catch (error) { console.error('Error in FAQ Accordion:', error); } // --- Activities Tabs --- // Index-based: content lives in the HTML panels (one per language), // so switching works regardless of the tab label language. try { const activityTabs = document.querySelectorAll('.activities-nav .activity-tab'); const activityPanels = document.querySelectorAll('.activities-panels .activity-panel'); if (activityTabs.length > 0 && activityPanels.length > 0) { activityTabs.forEach(tab => { tab.addEventListener('click', () => { const idx = tab.getAttribute('data-panel'); activityTabs.forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); }); activityPanels.forEach(p => { p.classList.remove('active'); p.hidden = true; }); tab.classList.add('active'); tab.setAttribute('aria-selected', 'true'); const panel = document.querySelector( `.activities-panels .activity-panel[data-panel="${idx}"]` ); if (panel) { panel.hidden = false; panel.classList.add('active'); } }); }); } } catch (error) { console.error('Error in Activities Tabs:', error); } /** * =================================================================== * 4. INTERAKTIVNA SEKCIJA "MISSION & VISION" * =================================================================== */ try { const missionVisionContainer = document.getElementById('mission-vision-interactive'); if (missionVisionContainer) { const missionBox = missionVisionContainer.querySelector('.mission-box'); const visionBox = missionVisionContainer.querySelector('.vision-box'); missionBox.addEventListener('mouseenter', () => { missionVisionContainer.classList.remove('show-vision'); }); visionBox.addEventListener('mouseenter', () => { missionVisionContainer.classList.add('show-vision'); }); } } catch(error) { console.error('Error in Mission/Vision interaction setup:', error); } /** * =================================================================== * 5. SCROLLSPY IN PROGRESS BAR ZA STRANI ČLANKOV (POSODOBLJENO) * =================================================================== */ try { const articlePage = document.querySelector('.article-page'); if (articlePage) { const articleContainer = articlePage.querySelector('.article-container'); const articleBody = articlePage.querySelector('.article-body'); const headings = articleBody ? Array.from(articleBody.querySelectorAll('h2')) : []; const articleTitleEl = articlePage.querySelector('.article-header h1'); const articleTitle = articleTitleEl ? articleTitleEl.textContent.trim() : (document.title || '').trim(); // Label for a heading in the table of contents. An optional // data-nav-title attribute lets an article override the full

// text with a shorter label; otherwise we use the heading text. const navLabel = (heading) => (heading.getAttribute('data-nav-title') || heading.textContent).trim(); // --- 5a. Zagotovi, da ima vsak naslov enoličen id (za sidra) --- // Transliterate then slugify so anchors are readable in every language: // Slovenian diacritics (č/š/ž/…) and Macedonian Cyrillic map to ASCII. // Anything still unmapped is dropped and falls back to section-N below. const TRANSLIT = { // Slovenian / Latin diacritics 'č':'c','ć':'c','š':'s','ž':'z','đ':'dj', 'á':'a','à':'a','â':'a','ä':'a','ã':'a','é':'e','è':'e','ê':'e','ë':'e', 'í':'i','ì':'i','î':'i','ï':'i','ó':'o','ò':'o','ô':'o','ö':'o','õ':'o', 'ú':'u','ù':'u','û':'u','ü':'u','ñ':'n','ç':'c','ß':'ss', // Macedonian Cyrillic 'а':'a','б':'b','в':'v','г':'g','д':'d','ѓ':'gj','е':'e','ж':'zh','з':'z', 'ѕ':'dz','и':'i','ј':'j','к':'k','л':'l','љ':'lj','м':'m','н':'n','њ':'nj', 'о':'o','п':'p','р':'r','с':'s','т':'t','ќ':'kj','у':'u','ф':'f','х':'h', 'ц':'c','ч':'ch','џ':'dj','ш':'sh' }; const usedIds = new Set(); const transliterate = (text) => text.replace(/[^\x00-\x7F]/g, (ch) => (ch in TRANSLIT ? TRANSLIT[ch] : '')); const slugify = (text) => transliterate(text.toLowerCase()) .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, ''); headings.forEach((heading, i) => { let id = heading.id || slugify(heading.textContent); if (id.length < 2) id = 'section-' + (i + 1); let unique = id, n = 2; while (usedIds.has(unique)) { unique = id + '-' + (n++); } usedIds.add(unique); heading.id = unique; }); // --- 5b. Zagotovi postavitveni ovoj in stransko vrstico (Desktop) --- // Older article pages (SI/MK and some EN) ship without the TOC // scaffolding, so we build it here from the page's own headings. let layoutContainer = articlePage.querySelector('.article-layout-container'); if (!layoutContainer && articleContainer) { layoutContainer = document.createElement('div'); layoutContainer.className = 'article-layout-container'; articleContainer.parentNode.insertBefore(layoutContainer, articleContainer); layoutContainer.appendChild(articleContainer); } let sidebar = articlePage.querySelector('.article-sidebar'); if (!sidebar && layoutContainer && headings.length > 0) { sidebar = document.createElement('aside'); sidebar.className = 'article-sidebar'; sidebar.innerHTML = ''; layoutContainer.insertBefore(sidebar, layoutContainer.firstChild); } const scrollspyNav = sidebar ? sidebar.querySelector('.scrollspy-nav') : null; const desktopUl = scrollspyNav ? scrollspyNav.querySelector('ul') : null; // --- 5c. Napolni namizni seznam, če je prazen (ohrani ročno napisanega) --- if (desktopUl && desktopUl.children.length === 0 && headings.length > 0) { headings.forEach(heading => { const li = document.createElement('li'); const a = document.createElement('a'); a.href = '#' + heading.id; a.textContent = navLabel(heading); li.appendChild(a); desktopUl.appendChild(li); }); } // --- 5d. Naslov članka nad kazalom (Desktop) --- if (sidebar && !sidebar.querySelector('.article-sidebar-title') && articleTitle) { const titleEl = document.createElement('p'); titleEl.className = 'article-sidebar-title'; titleEl.textContent = articleTitle; sidebar.insertBefore(titleEl, sidebar.firstChild); } // --- 5e. Zagotovi lepljivo mobilno glavo z naslovom + progress bar --- let mobileHeader = articlePage.querySelector('.mobile-article-header'); if (!mobileHeader && headings.length > 0) { mobileHeader = document.createElement('div'); mobileHeader.className = 'mobile-article-header'; mobileHeader.innerHTML = '' + '' + '
' + '
'; articlePage.insertBefore(mobileHeader, articlePage.firstChild); } const mobileTitle = document.getElementById('mobile-article-title'); if (mobileTitle && !mobileTitle.textContent.trim()) { mobileTitle.textContent = articleTitle; // known at load; scrollspy refines it } const mobileDropdown = articlePage.querySelector('.mobile-scrollspy-dropdown'); const mobileLinksContainer = document.getElementById('mobile-scrollspy-links'); const progressBar = articlePage.querySelector('.progress-bar'); const desktopNavLinks = desktopUl ? Array.from(desktopUl.querySelectorAll('a')) : []; // --- 5f. Napolni mobilni spustni seznam --- if (headings.length > 0 && mobileLinksContainer && mobileLinksContainer.children.length === 0) { headings.forEach(heading => { const listItem = document.createElement('li'); const link = document.createElement('a'); link.href = '#' + heading.id; link.textContent = navLabel(heading); listItem.appendChild(link); mobileLinksContainer.appendChild(listItem); }); } // --- 5g. Odpiranje/zapiranje mobilnega spustnega seznama --- if (mobileHeader && mobileDropdown) { mobileHeader.addEventListener('click', (e) => { if (!e.target.closest('a')) { mobileHeader.classList.toggle('open'); mobileDropdown.classList.toggle('open'); } }); mobileDropdown.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { mobileHeader.classList.remove('open'); mobileDropdown.classList.remove('open'); }); }); } // --- 5h. Progress Bar --- function updateProgressBar() { if (!progressBar) return; const scrollableHeight = document.documentElement.scrollHeight - window.innerHeight; const scrollTop = window.scrollY; progressBar.style.width = scrollableHeight > 0 ? `${(scrollTop / scrollableHeight) * 100}%` : '0%'; } // --- 5i. Scrollspy (posodobi navigacijo, naslov in URL sidro) --- if (headings.length > 0) { let activeId = null; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (!entry.isIntersecting) return; const id = entry.target.id; if (id === activeId) return; activeId = id; // Namizna navigacija const desktopNavLink = desktopNavLinks.find(link => link.getAttribute('href') === `#${id}`); if (desktopNavLink) { desktopNavLinks.forEach(link => link.classList.remove('active')); desktopNavLink.classList.add('active'); } // Mobilni naslov + aktivna povezava if (mobileTitle) mobileTitle.textContent = navLabel(entry.target); if (mobileLinksContainer) { const mobileNavLink = mobileLinksContainer.querySelector(`a[href="#${id}"]`); if (mobileNavLink) { mobileLinksContainer.querySelectorAll('a').forEach(link => link.classList.remove('active')); mobileNavLink.classList.add('active'); } } // Posodobi URL sidro brez skoka in brez polnjenja zgodovine if (window.history && history.replaceState) { history.replaceState(null, '', '#' + id); } }); }, { rootMargin: "-100px 0px -55% 0px" // Sproži, ko je naslov v zgornjem delu zaslona }); headings.forEach(heading => observer.observe(heading)); } window.addEventListener('scroll', updateProgressBar); updateProgressBar(); } } catch (error) { console.error('Error in Article Scrollspy/Progress Bar setup:', error); } /** * =================================================================== * 6. FILTER KATEGORIJ ZA SEZNAM PROJEKTOV / OBJAV * =================================================================== * * Deluje na katerikoli strani s seznamom (projekti, novice, ...). * Popolnoma jezikovno neodvisen: gumbi nosijo stabilni ključ * (data-filter / data-subfilter), kartice pa data-category / * data-subcategory. Oznake so lahko v katerem koli jeziku. */ try { const grid = document.querySelector('.posts-grid'); if (grid) { const filterSection = document.querySelector('.filter-section'); const pagination = document.querySelector('.pagination'); const cards = Array.from(grid.querySelectorAll('.post-card')); const emptyMsg = document.querySelector('.filter-empty'); // Koliko kartic na stran. Ko bo objav več kot toliko, se // paginacija samodejno vklopi; do takrat prikaže eno stran. const PAGE_SIZE = 9; let activeCat = 'all'; let activeSub = 'all'; let currentPage = 1; let matching = cards.slice(); // --- Paginacija --- const pageNumbers = pagination ? pagination.querySelector('.pagination-numbers') : null; const arrows = pagination ? pagination.querySelectorAll('.pagination-arrow') : []; const prevArrow = arrows.length ? arrows[0] : null; const nextArrow = arrows.length > 1 ? arrows[arrows.length - 1] : null; const totalPages = () => Math.max(1, Math.ceil(matching.length / PAGE_SIZE)); const renderPageNumbers = (total) => { if (!pageNumbers) return; pageNumbers.innerHTML = ''; const addNum = (n) => { const a = document.createElement('a'); a.href = '#'; a.className = 'page-number' + (n === currentPage ? ' active' : ''); a.textContent = n; a.addEventListener('click', (e) => { e.preventDefault(); goToPage(n); }); pageNumbers.appendChild(a); }; const addDots = () => { const s = document.createElement('span'); s.className = 'page-dots'; s.textContent = '...'; pageNumbers.appendChild(s); }; // Do 7 strani pokažemo vse; sicer okno okoli trenutne strani. const seq = []; if (total <= 7) { for (let i = 1; i <= total; i++) seq.push(i); } else { seq.push(1); const start = Math.max(2, currentPage - 1); const end = Math.min(total - 1, currentPage + 1); if (start > 2) seq.push('...'); for (let i = start; i <= end; i++) seq.push(i); if (end < total - 1) seq.push('...'); seq.push(total); } seq.forEach(p => (p === '...' ? addDots() : addNum(p))); }; const render = () => { const total = totalPages(); if (currentPage > total) currentPage = total; if (currentPage < 1) currentPage = 1; cards.forEach(c => { c.hidden = true; }); const start = (currentPage - 1) * PAGE_SIZE; matching.slice(start, start + PAGE_SIZE).forEach(c => { c.hidden = false; }); if (emptyMsg) emptyMsg.hidden = matching.length !== 0; if (pagination) { // Skrij paginacijo, ko ni zadetkov. pagination.hidden = matching.length === 0; renderPageNumbers(total); if (prevArrow) prevArrow.classList.toggle('disabled', currentPage <= 1); if (nextArrow) nextArrow.classList.toggle('disabled', currentPage >= total); } }; function goToPage(n) { const total = totalPages(); currentPage = Math.min(Math.max(1, n), total); render(); // Po menjavi strani se pomaknemo na vrh seznama. const anchor = filterSection || grid; const y = anchor.getBoundingClientRect().top + window.scrollY - 100; window.scrollTo({ top: y, behavior: 'smooth' }); } if (prevArrow) { prevArrow.addEventListener('click', (e) => { e.preventDefault(); if (currentPage > 1) goToPage(currentPage - 1); }); } if (nextArrow) { nextArrow.addEventListener('click', (e) => { e.preventDefault(); if (currentPage < totalPages()) goToPage(currentPage + 1); }); } // --- Filter (če je na strani) --- const computeMatching = () => { matching = cards.filter(card => { const cat = (card.getAttribute('data-category') || '').trim(); const sub = (card.getAttribute('data-subcategory') || '').trim(); let show = activeCat === 'all' || cat === activeCat; if (show && activeCat !== 'all' && activeSub !== 'all') { show = sub === activeSub; } return show; }); }; if (filterSection) { const pills = Array.from(filterSection.querySelectorAll('.filter-pill')); const subnavs = Array.from(filterSection.querySelectorAll('.filter-subnav')); const showSubnavFor = (cat) => { subnavs.forEach(sn => { const match = sn.getAttribute('data-parent') === cat; sn.hidden = !match; if (match) { sn.querySelectorAll('.filter-chip').forEach(chip => chip.classList.toggle('active', (chip.getAttribute('data-subfilter') || 'all') === 'all')); } }); }; pills.forEach(pill => { pill.addEventListener('click', () => { activeCat = pill.getAttribute('data-filter') || 'all'; activeSub = 'all'; currentPage = 1; pills.forEach(p => { const on = p === pill; p.classList.toggle('active', on); p.setAttribute('aria-pressed', on ? 'true' : 'false'); }); showSubnavFor(activeCat); computeMatching(); render(); }); }); subnavs.forEach(sn => { sn.querySelectorAll('.filter-chip').forEach(chip => { chip.addEventListener('click', () => { activeSub = chip.getAttribute('data-subfilter') || 'all'; currentPage = 1; sn.querySelectorAll('.filter-chip').forEach(c => c.classList.toggle('active', c === chip)); computeMatching(); render(); }); }); }); // Začetno stanje: "Vse" aktivno, podfiltri skriti. showSubnavFor(activeCat); } computeMatching(); render(); } } catch (error) { console.error('Error in Project Filter/Pagination setup:', error); } /** * =================================================================== * 7. NEWSLETTER (MailerLite) * =================================================================== */ function setupNewsletter() { // --- MailerLite configuration ----------------------------------- // Fill these in from your MailerLite embedded form // (Forms > Embedded forms > your form > Embed code). Both values // appear in the embed's subscribe URL: // https://assets.mailerlite.com/jsonp//forms/
/subscribe const ML_ACCOUNT = '1253161'; const ML_FORM = '194374752800868123'; // ---------------------------------------------------------------- const forms = document.querySelectorAll('form.subscribe-form, form.subscribe-form-bottom'); if (!forms.length) return; let lang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase(); if (lang === 'sl') lang = 'si'; // maps to the site's /si/ locale if (['en', 'si', 'mk'].indexOf(lang) === -1) lang = 'en'; const T = { invalid: { en: 'Please enter a valid email address.', si: 'Vnesite veljaven e-poštni naslov.', mk: 'Внесете важечка е-адреса.' }, sending: { en: 'Sending…', si: 'Pošiljanje…', mk: 'Се испраќа…' }, success: { en: 'Thank you! Please check your inbox to confirm your subscription.', si: 'Hvala! Za potrditev naročnine preverite svoj e-poštni predal.', mk: 'Ви благодариме! Проверете го вашето сандаче за да ја потврдите претплатата.' }, error: { en: 'Something went wrong. Please try again later.', si: 'Nekaj je šlo narobe. Poskusite znova pozneje.', mk: 'Нешто не успеа. Обидете се повторно подоцна.' }, notready: { en: 'The newsletter is being set up. Please check back soon.', si: 'Novice se pravkar nastavljajo. Preverite kmalu znova.', mk: 'Билтенот се поставува. Проверете повторно наскоро.' } }; const t = (k) => (T[k][lang] || T[k].en); forms.forEach((form) => { const input = form.querySelector('input[type="email"], input[type="text"], input'); if (!input) return; input.setAttribute('type', 'email'); input.setAttribute('name', 'fields[email]'); input.setAttribute('required', ''); input.setAttribute('autocomplete', 'email'); const status = document.createElement('p'); status.className = 'subscribe-status'; status.setAttribute('role', 'status'); status.setAttribute('aria-live', 'polite'); status.hidden = true; form.parentNode.insertBefore(status, form.nextSibling); const show = (msg, type) => { status.textContent = msg; status.className = 'subscribe-status is-' + type; status.hidden = false; }; form.addEventListener('submit', (e) => { e.preventDefault(); const email = (input.value || '').trim(); if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { show(t('invalid'), 'error'); input.focus(); return; } if (!ML_ACCOUNT || !ML_FORM) { show(t('notready'), 'error'); return; } const button = form.querySelector('button[type="submit"], button'); if (button) button.disabled = true; show(t('sending'), 'info'); const url = 'https://assets.mailerlite.com/jsonp/' + ML_ACCOUNT + '/forms/' + ML_FORM + '/subscribe'; const body = new URLSearchParams(); body.append('fields[email]', email); body.append('ml-submit', '1'); body.append('anticsrf', 'true'); fetch(url, { method: 'POST', body: body, mode: 'no-cors' }) .then(() => { // Double opt-in: MailerLite emails a confirmation link, so we // optimistically confirm here (the opt-in email is the real check). form.reset(); show(t('success'), 'success'); }) .catch(() => show(t('error'), 'error')) .finally(() => { if (button) button.disabled = false; }); }); }); } /** * =================================================================== * LANGUAGE SUGGESTION BANNER (browser-language based, dismissible) * =================================================================== */ function setupLanguageSuggestion() { try { var STORE = 'msos-lang-banner'; if (localStorage.getItem(STORE) === 'dismissed') return; var validLangs = ['en', 'si', 'mk']; // Current page language (; site uses /si/ for Slovene "sl"). var docLang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase(); if (docLang === 'sl') docLang = 'si'; // Detect the visitor's preferred language from the browser. var navLangs = navigator.languages || [navigator.language || '']; var detected = null; for (var i = 0; i < navLangs.length; i++) { var l = (navLangs[i] || '').toLowerCase(); if (l.indexOf('mk') === 0) { detected = 'mk'; break; } if (l.indexOf('sl') === 0) { detected = 'si'; break; } if (l.indexOf('en') === 0) { detected = 'en'; break; } } if (!detected || detected === docLang) return; // unknown or already correct // Build the equivalent URL in the detected language (same logic as the switcher). var path = window.location.pathname; var parts = path.split('/').filter(Boolean); var pathWithoutLang = path; if (parts.length > 0 && validLangs.indexOf(parts[0]) !== -1) { pathWithoutLang = '/' + parts.slice(1).join('/'); if (path.endsWith('/') && pathWithoutLang !== '/') pathWithoutLang += '/'; } var targetUrl = ('/' + detected + pathWithoutLang).replace(/\/{2,}/g, '/'); var MSG = { en: { text: 'This page is also available in English.', btn: 'View in English', flag: 'gb', aria: 'Language suggestion' }, mk: { text: 'Оваа страница е достапна и на македонски.', btn: 'Прикажи на македонски', flag: 'mk', aria: 'Предлог за јазик' }, si: { text: 'Ta stran je na voljo tudi v slovenščini.', btn: 'Prikaži v slovenščini', flag: 'si', aria: 'Predlog jezika' } }; var m = MSG[detected]; function showBanner() { if (document.querySelector('.lang-suggest')) return; var bar = document.createElement('div'); bar.className = 'lang-suggest'; bar.setAttribute('role', 'region'); bar.setAttribute('aria-label', m.aria); bar.innerHTML = '' + '

' + m.text + '

' + '' + m.btn + '' + ''; document.body.appendChild(bar); requestAnimationFrame(function () { bar.classList.add('is-visible'); }); function remember() { try { localStorage.setItem(STORE, 'dismissed'); } catch (e) {} } bar.querySelector('.lang-suggest-btn').addEventListener('click', remember); bar.querySelector('.lang-suggest-close').addEventListener('click', function () { remember(); bar.classList.remove('is-visible'); setTimeout(function () { bar.remove(); }, 300); }); } // Don't collide with the cookie-consent card (also a bottom-centred fixed card). // If the visitor hasn't answered consent yet, wait until they do, then show. var consentDecided; try { var c = localStorage.getItem('msos-consent'); consentDecided = (c === 'granted' || c === 'denied'); } catch (e) { consentDecided = true; } if (consentDecided && !document.querySelector('.msos-cc')) { showBanner(); } else if ('MutationObserver' in window) { var obs = new MutationObserver(function () { if (!document.querySelector('.msos-cc')) { obs.disconnect(); showBanner(); } }); obs.observe(document.body, { childList: true }); } else { // Fallback: poll until the consent card is gone. var tries = 0; var iv = setInterval(function () { if (!document.querySelector('.msos-cc') || ++tries > 120) { clearInterval(iv); showBanner(); } }, 500); } } catch (error) { console.error('Error in language suggestion banner:', error); } } /** * =================================================================== * GALLERY LIGHTBOX (per-album photo viewer) * =================================================================== */ function setupGallery() { var lb = document.getElementById('gallery-lightbox'); if (!lb) return; var img = lb.querySelector('.lightbox-img'); var cap = lb.querySelector('.lightbox-caption'); var group = []; var idx = 0; function render() { var it = group[idx]; img.setAttribute('src', it.full); img.setAttribute('alt', it.caption || ''); cap.textContent = (it.caption || '') + (group.length > 1 ? ' (' + (idx + 1) + '/' + group.length + ')' : ''); } function open(grid, start) { group = Array.prototype.map.call(grid.querySelectorAll('.gallery-thumb'), function (b) { return { full: b.getAttribute('data-full'), caption: b.getAttribute('data-caption') || '' }; }); idx = start; render(); lb.classList.add('is-open'); lb.setAttribute('aria-hidden', 'false'); document.documentElement.style.overflow = 'hidden'; } function close() { lb.classList.remove('is-open'); lb.setAttribute('aria-hidden', 'true'); document.documentElement.style.overflow = ''; img.setAttribute('src', ''); } function step(d) { idx = (idx + d + group.length) % group.length; render(); } document.querySelectorAll('.gallery-grid').forEach(function (grid) { grid.querySelectorAll('.gallery-thumb').forEach(function (btn, i) { btn.addEventListener('click', function () { open(grid, i); }); }); }); lb.querySelector('.lightbox-close').addEventListener('click', close); lb.querySelector('.lightbox-prev').addEventListener('click', function () { step(-1); }); lb.querySelector('.lightbox-next').addEventListener('click', function () { step(1); }); lb.addEventListener('click', function (e) { if (e.target === lb) close(); }); document.addEventListener('keydown', function (e) { if (!lb.classList.contains('is-open')) return; if (e.key === 'Escape') close(); else if (e.key === 'ArrowLeft') step(-1); else if (e.key === 'ArrowRight') step(1); }); } /** * =================================================================== * HUB USE-CASE CAROUSEL (auto-scrolling) * =================================================================== */ function setupHubCarousel() { const trackEl = document.querySelector('.hub-track'); if (!trackEl) return; const group = trackEl.querySelector('.hub-group'); if (!group) return; // Duplicate the group so translateX(-50%) loops back seamlessly (waterslide flow). // Guard against double-cloning if the script somehow runs twice. if (trackEl.querySelectorAll('.hub-group').length < 2) { const clone = group.cloneNode(true); clone.setAttribute('aria-hidden', 'true'); trackEl.appendChild(clone); } } /** * =================================================================== * 8. ZAGON FUNKCIJ PO NALOŽITVI STRANI * =================================================================== */ highlightActiveLink(); setupLanguageSwitcher(); setupLanguageSuggestion(); setupNewsletter(); setupHubCarousel(); setupGallery(); });