// Page script for /pages/realisations.html document.addEventListener('DOMContentLoaded', async () => { const dataUrl = '../assets/data/projects.json'; let allProjects = []; try { const res = await fetch(dataUrl); if (!res.ok) throw new Error(res.statusText); allProjects = await res.json(); } catch (err) { console.error('Failed to load projects data:', err); return; } const ITEMS_PER_PAGE = 9; let currentPage = 1; let currentFilter = 'all'; const portfolioGrid = document.getElementById('portfolio-grid'); const paginationContainer = document.getElementById('pagination'); const filterTabs = document.querySelectorAll('.filter-tab'); const modalOverlay = document.getElementById('project-modal-overlay'); const modalClose = document.getElementById('project-modal-close'); const gallerySlides = document.getElementById('gallery-slides'); const galleryPrev = document.getElementById('gallery-prev'); const galleryNext = document.getElementById('gallery-next'); const modalTitle = document.getElementById('modal-title'); const modalCategory = document.getElementById('modal-category'); const modalDesc = document.getElementById('modal-desc'); const modalMeta = document.getElementById('modal-meta'); const modalFeatures = document.getElementById('modal-features'); const modalTech = document.getElementById('modal-tech'); const modalStats = document.getElementById('modal-stats'); function getFilteredProjects() { if (currentFilter === 'all') return [...allProjects]; return allProjects.filter(p => p.category === currentFilter); } function getTotalPages(filtered) { return Math.ceil(filtered.length / ITEMS_PER_PAGE) || 1; } function getProjectsForPage(filtered, page) { const start = (page - 1) * ITEMS_PER_PAGE; return filtered.slice(start, start + ITEMS_PER_PAGE); } function getCategoryLabel(category) { const labels = { 'bot': 'Bot Discord', 'map': 'Fortnite Map', 'graphisme': 'Graphisme' }; return labels[category] || category; } function renderProjects() { const filtered = getFilteredProjects(); const projects = getProjectsForPage(filtered, currentPage); portfolioGrid.innerHTML = projects.map(project => `
${getCategoryLabel(project.category)}

${project.title}

${project.desc.substring(0, 80)}...

`).join(''); // activate reveals & icons setTimeout(() => { document.querySelectorAll('.portfolio-item.reveal').forEach(el => el.classList.add('active')); }, 80); // ensure icons are replaced for newly injected content if (window.feather && feather.replace) { try { feather.replace(); } catch (e) {} } } function renderPagination() { const filtered = getFilteredProjects(); const totalPages = getTotalPages(filtered); if (totalPages <= 1) { paginationContainer.innerHTML = ''; return; } let html = ``; const maxVisible = 5; let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2)); let endPage = Math.min(totalPages, startPage + maxVisible - 1); if (endPage - startPage < maxVisible - 1) startPage = Math.max(1, endPage - maxVisible + 1); if (startPage > 1) { html += ``; if (startPage > 2) html += `...`; } for (let i = startPage; i <= endPage; i++) { html += ``; } if (endPage < totalPages) { if (endPage < totalPages - 1) html += `...`; html += ``; } html += ``; const startItem = (currentPage - 1) * ITEMS_PER_PAGE + 1; const endItem = Math.min(currentPage * ITEMS_PER_PAGE, filtered.length); html += `
Affichage ${startItem}-${endItem} sur ${filtered.length} projets
`; paginationContainer.innerHTML = html; if (window.feather && feather.replace) { try { feather.replace(); } catch (e) {} } } function goToPage(page) { const filtered = getFilteredProjects(); const totalPages = getTotalPages(filtered); if (page < 1 || page > totalPages) return; currentPage = page; renderProjects(); renderPagination(); portfolioGrid.scrollIntoView({ behavior: 'smooth', block: 'start' }); } function openProjectModal(projectId) { const project = allProjects.find(p => p.id === Number(projectId)); if (!project) return; modalCategory.textContent = getCategoryLabel(project.category); modalTitle.textContent = project.title; modalDesc.textContent = project.desc; modalMeta.innerHTML = ` ${project.year} ${project.duration}`; modalFeatures.innerHTML = project.features.map(f => `
${f}
`).join(''); modalTech.innerHTML = project.tech.map(t => `
${t}
`).join(''); modalStats.innerHTML = project.stats.map(s => `
${s.value}
${s.label}
`).join(''); // gallery slides (use gradient as placeholder slides like original) gallerySlides.innerHTML = ` `; // reset gallery state galleryIndex = 0; updateGallery(); if (modalOverlay) modalOverlay.classList.add('active'); if (window.feather && feather.replace) { try { feather.replace(); } catch (e) {} } } function closeProjectModal() { if (modalOverlay) modalOverlay.classList.remove('active'); } // Gallery helpers let galleryIndex = 0; function updateGallery() { const slides = gallerySlides ? Array.from(gallerySlides.children) : []; slides.forEach((s, i) => { s.style.transform = `translateX(${(i - galleryIndex) * 100}%)`; s.classList.toggle('active', i === galleryIndex); }); } function prevSlide() { galleryIndex = Math.max(0, galleryIndex - 1); updateGallery(); } function nextSlide() { const slides = gallerySlides ? gallerySlides.children.length : 0; galleryIndex = Math.min(slides - 1, galleryIndex + 1); updateGallery(); } // Event bindings filterTabs.forEach(tab => tab.addEventListener('click', (e) => { filterTabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); currentFilter = tab.dataset.filter || 'all'; currentPage = 1; renderProjects(); renderPagination(); })); paginationContainer.addEventListener('click', (e) => { const btn = e.target.closest('button[data-page]'); if (!btn) return; const page = Number(btn.getAttribute('data-page')); goToPage(page); }); portfolioGrid.addEventListener('click', (e) => { const item = e.target.closest('.portfolio-item'); if (!item) return; const id = item.getAttribute('data-project-id'); openProjectModal(id); }); if (modalClose) modalClose.addEventListener('click', closeProjectModal); if (modalOverlay) modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) closeProjectModal(); }); window.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modalOverlay.classList.contains('active')) closeProjectModal(); }); if (galleryPrev) galleryPrev.addEventListener('click', prevSlide); if (galleryNext) galleryNext.addEventListener('click', nextSlide); // Expose for compatibility window.openProjectModal = openProjectModal; window.goToPage = goToPage; // Initial render renderProjects(); renderPagination(); });