734 lines
33 KiB
JavaScript
734 lines
33 KiB
JavaScript
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);
|
||
}
|
||
|
||
|
||
/**
|
||
* ===================================================================
|
||
* 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;
|
||
}
|
||
|
||
const docLang = (document.documentElement.getAttribute('lang') || 'en').toLowerCase();
|
||
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);
|
||
});
|
||
|
||
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 <h2>
|
||
// 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 = '<nav class="scrollspy-nav"><ul></ul></nav>';
|
||
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 =
|
||
'<span id="mobile-article-title"></span>' +
|
||
'<i class="fas fa-chevron-down"></i>' +
|
||
'<div class="progress-bar-container"><div class="progress-bar"></div></div>' +
|
||
'<div class="mobile-scrollspy-dropdown"><ul id="mobile-scrollspy-links"></ul></div>';
|
||
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/<ACCOUNT>/forms/<FORM>/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 (['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; });
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* ===================================================================
|
||
* 8. ZAGON FUNKCIJ PO NALOŽITVI STRANI
|
||
* ===================================================================
|
||
*/
|
||
highlightActiveLink();
|
||
setupLanguageSwitcher();
|
||
setupNewsletter();
|
||
|
||
}); |