228 lines
8.6 KiB
JavaScript
228 lines
8.6 KiB
JavaScript
// 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 => `
|
|
<div class="portfolio-item reveal" data-project-id="${project.id}">
|
|
<div class="portfolio-bg" style="background: ${project.gradient};"></div>
|
|
<div class="portfolio-content">
|
|
<span class="portfolio-tag">${getCategoryLabel(project.category)}</span>
|
|
<h4>${project.title}</h4>
|
|
<p>${project.desc.substring(0, 80)}...</p>
|
|
</div>
|
|
</div>
|
|
`).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 = `<button class="pagination-btn pagination-arrow" data-page="${Math.max(1, currentPage - 1)}" ${currentPage === 1 ? 'disabled' : ''}><i data-feather="chevron-left"></i></button>`;
|
|
|
|
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 += `<button class="pagination-btn" data-page="1">1</button>`;
|
|
if (startPage > 2) html += `<span class="pagination-ellipsis">...</span>`;
|
|
}
|
|
|
|
for (let i = startPage; i <= endPage; i++) {
|
|
html += `<button class="pagination-btn ${i === currentPage ? 'active' : ''}" data-page="${i}">${i}</button>`;
|
|
}
|
|
|
|
if (endPage < totalPages) {
|
|
if (endPage < totalPages - 1) html += `<span class="pagination-ellipsis">...</span>`;
|
|
html += `<button class="pagination-btn" data-page="${totalPages}">${totalPages}</button>`;
|
|
}
|
|
|
|
html += `<button class="pagination-btn pagination-arrow" data-page="${Math.min(totalPages, currentPage + 1)}" ${currentPage === totalPages ? 'disabled' : ''}><i data-feather="chevron-right"></i></button>`;
|
|
|
|
const startItem = (currentPage - 1) * ITEMS_PER_PAGE + 1;
|
|
const endItem = Math.min(currentPage * ITEMS_PER_PAGE, filtered.length);
|
|
html += `<div class="pagination-info">Affichage ${startItem}-${endItem} sur ${filtered.length} projets</div>`;
|
|
|
|
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 = `<span><i data-feather="calendar"></i> ${project.year}</span> <span><i data-feather="clock"></i> ${project.duration}</span>`;
|
|
|
|
modalFeatures.innerHTML = project.features.map(f => `<div class="project-feature-item">${f}</div>`).join('');
|
|
modalTech.innerHTML = project.tech.map(t => `<div class="project-tech-item">${t}</div>`).join('');
|
|
modalStats.innerHTML = project.stats.map(s => `
|
|
<div class="project-stat-card">
|
|
<div class="project-stat-card-value">${s.value}</div>
|
|
<div class="project-stat-card-label">${s.label}</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
// gallery slides (use gradient as placeholder slides like original)
|
|
gallerySlides.innerHTML = `
|
|
<div class="project-gallery-slide" style="background: ${project.gradient};"></div>
|
|
<div class="project-gallery-slide" style="background: ${project.gradient}; filter: hue-rotate(30deg);"></div>
|
|
<div class="project-gallery-slide" style="background: ${project.gradient}; filter: hue-rotate(-30deg);"></div>
|
|
`;
|
|
|
|
// 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();
|
|
});
|