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 {
// Accessibility: the icon is a
, so expose it to keyboard and
// screen-reader users as a button (focusable, labelled, toggle state).
if (!icon.hasAttribute('role')) icon.setAttribute('role', 'button');
if (!icon.hasAttribute('tabindex')) icon.setAttribute('tabindex', '0');
if (!icon.hasAttribute('aria-label')) icon.setAttribute('aria-label', 'Open menu');
icon.setAttribute('aria-expanded', 'false');
const setMenu = (open) => {
root.classList.toggle('nav-open', open);
icon.setAttribute('aria-expanded', open ? 'true' : 'false');
icon.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
};
icon.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
setMenu(!root.classList.contains('nav-open'));
});
// Activate with Enter / Space like a native button.
icon.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') {
e.preventDefault();
setMenu(!root.classList.contains('nav-open'));
}
});
document.addEventListener('click', (e) => {
if (root.classList.contains('nav-open') && !e.target.closest('.mobile-nav-panel') && !e.target.closest('.mobile-menu-icon')) {
setMenu(false);
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
setMenu(false);
}
});
}
} 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/