// Home page specific JS: team carousel + modal + portfolio preview + partners + konami document.addEventListener('DOMContentLoaded', () => { // ── Portfolio preview — même cartes que la page réalisations ────────────────── const homePortfolioGrid = document.getElementById('home-portfolio-grid'); if (homePortfolioGrid) { fetch('assets/data/projects.json') .then(res => res.json()) .then(projects => { const first3 = projects.slice(0, 3); const delayClasses = ['', ' reveal-delay-1', ' reveal-delay-2']; const categoryLabels = { bot: 'Bot Discord', map: 'Fortnite Map', graphisme: 'Graphisme' }; homePortfolioGrid.innerHTML = first3.map((p, i) => { const firstFile = Array.isArray(p.files) && p.files.length > 0 ? p.files[0] : null; const imgSrc = firstFile ? (p.images || '') + firstFile : null; return `
${imgSrc ? `${p.title}` : ''}
${categoryLabels[p.category] || p.category}

${p.title}

`; }).join(''); setTimeout(() => { homePortfolioGrid.querySelectorAll('.reveal').forEach(el => el.classList.add('active')); if (window.feather && feather.replace) try { feather.replace(); } catch(e) {} }, 80); // Délégation globale sur document — fonctionne même si realisations.js // se charge après home.js (ordre des scripts defer non garanti) homePortfolioGrid.addEventListener('click', e => { const item = e.target.closest('.portfolio-item[data-project-id]'); if (!item) return; const id = item.getAttribute('data-project-id'); if (typeof window.openProjectModal === 'function') { window.openProjectModal(id); } else { // Retry : realisations.js pas encore initialisé, on attend const wait = setInterval(() => { if (typeof window.openProjectModal === 'function') { clearInterval(wait); window.openProjectModal(id); } }, 50); // Abandon après 3 secondes setTimeout(() => clearInterval(wait), 3000); } }); }) .catch(err => console.error('Failed to load projects for home preview', err)); } // ── Partners — carousel + modal ───────────────────────────────────────────── const partnersTrack = document.getElementById('partners-track'); const partnerModalOverlay = document.getElementById('partner-modal-overlay'); if (partnersTrack) { fetch('assets/data/partners.json') .then(res => res.json()) .then(partners => { if (!partners.length) { const section = document.getElementById('partenaires'); if (section) section.style.display = 'none'; return; } // Générer les slides (tripler pour garantir boucle sans trou) const copies = [...partners, ...partners, ...partners]; partnersTrack.innerHTML = copies.map((p, i) => { const imgSrc = p.logo || `assets/images/partners/${p.name}.png`; return `
${p.displayName} ${p.displayName}
`; }).join(''); // Défilement infini — loopWidth mesuré une seule fois après rendu complet const track = partnersTrack; let isPaused = false; let scrollPos = 0; let loopWidth = 0; let animFrame; const SPEED = 0.6; function measureLoop() { // Mesure la largeur exacte d'un bloc (1/3 des slides) // On utilise offsetWidth de chaque slide réel + gap pour éviter les arrondis de scrollWidth const slides = Array.from(track.querySelectorAll('.partner-slide')); const perGroup = partners.length; if (slides.length < perGroup) return 0; // Largeur d'un groupe = somme des (offsetWidth + gap) des premiers slides const style = window.getComputedStyle(track); const gap = parseFloat(style.gap) || 24; let w = 0; for (let i = 0; i < perGroup; i++) { w += slides[i].offsetWidth + gap; } return Math.round(w); } function autoScroll() { if (!isPaused) { scrollPos += SPEED; if (loopWidth > 0 && scrollPos >= loopWidth) { scrollPos -= loopWidth; } track.style.transform = `translateX(-${scrollPos}px)`; } animFrame = requestAnimationFrame(autoScroll); } // Attendre que les slides soient rendues et mesurées requestAnimationFrame(() => { requestAnimationFrame(() => { loopWidth = measureLoop(); animFrame = requestAnimationFrame(autoScroll); }); }); // Re-mesurer au resize pour rester précis let resizeTO; window.addEventListener('resize', () => { clearTimeout(resizeTO); resizeTO = setTimeout(() => { loopWidth = measureLoop(); }, 150); }); track.parentElement.addEventListener('mouseenter', () => { isPaused = true; }); track.parentElement.addEventListener('mouseleave', () => { isPaused = false; }); // Click → modal track.querySelectorAll('.partner-slide').forEach(slide => { const open = () => { const idx = parseInt(slide.dataset.partnerIdx, 10); openPartnerModal(partners[idx]); }; slide.addEventListener('click', open); slide.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); open(); } }); }); function openPartnerModal(p) { isPaused = true; const imgSrc = p.logo || `assets/images/partners/${p.name}.png`; const logoImg = document.getElementById('partner-modal-logo-img'); const logoInner = document.getElementById('partner-modal-logo-fallback'); logoImg.src = imgSrc; logoImg.alt = p.displayName; logoImg.style.display = 'block'; logoInner.style.background = ''; logoInner.style.justifyContent = ''; // Si l'image échoue → fallback initiales dans l'octogone logoImg.onerror = function() { this.style.display = 'none'; logoInner.style.background = p.gradient; logoInner.style.display = 'flex'; logoInner.style.alignItems = 'center'; logoInner.style.justifyContent = 'center'; logoInner.style.fontSize = '2rem'; logoInner.style.fontFamily = "'OmegaLarge', sans-serif"; logoInner.style.color = 'white'; logoInner.textContent = p.displayName.substring(0,2).toUpperCase(); }; document.getElementById('partner-modal-name').textContent = p.displayName; document.getElementById('partner-modal-desc').textContent = p.longDesc; document.getElementById('partner-modal-since').textContent = p.since; document.getElementById('partner-modal-services').innerHTML = (p.services || []).map(s => `${s}`).join(''); document.getElementById('partner-modal-stats').innerHTML = (p.stats || []).map(s => `
${s.value}
${s.label}
`).join(''); const links = document.getElementById('partner-modal-links'); // Réseaux sociaux depuis le champ social du partenaire links.innerHTML = typeof window.renderSocialLinks === 'function' ? window.renderSocialLinks(p.social) : ''; if (window.feather) feather.replace(); partnerModalOverlay.classList.add('active'); document.body.style.overflow = 'hidden'; } if (partnerModalOverlay) { const closeBtn = document.getElementById('partner-modal-close'); closeBtn && closeBtn.addEventListener('click', closePartnerModal); partnerModalOverlay.addEventListener('click', e => { if (e.target === partnerModalOverlay) closePartnerModal(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && partnerModalOverlay.classList.contains('active')) closePartnerModal(); }); } function closePartnerModal() { partnerModalOverlay.classList.remove('active'); document.body.style.overflow = ''; isPaused = false; } }) .catch(err => { const section = document.getElementById('partenaires'); if (section) section.style.display = 'none'; }); } // ── Team section ───────────────────────────────────────────────────────────── fetch('assets/data/team.json') .then(res => res.json()) .then(members => { initTeamSection(members); }) .catch(err => { console.error('Failed to load team data', err); }); // Konami + easter egg → définis au niveau top-level du script (voir bas de fichier) // ── initTeamSection ────────────────────────────────────────────────────────── function initTeamSection(members) { const carousel = document.getElementById('team-carousel'); const dotsContainer = document.getElementById('carousel-dots'); const prevBtn = document.getElementById('carousel-prev'); const nextBtn = document.getElementById('carousel-next'); carousel.innerHTML = members.map((m, i) => { const imgSrc = `assets/images/team/${m.id}.png`; return `
${m.name}

${m.name}

${m.role}

${(m.bio || '').split('.').shift()}.

`; }).join(''); let cards = Array.from(carousel.querySelectorAll('.team-card')); let currentIndex = 0; let cardsPerView = 3; let autoPlayInterval; function updateCardsPerView() { if (window.innerWidth <= 768) cardsPerView = 1; else if (window.innerWidth <= 1024) cardsPerView = 2; else cardsPerView = 3; } function getTotalDots() { return Math.max(1, cards.length - cardsPerView + 1); } function createDots() { dotsContainer.innerHTML = ''; for (let i = 0; i < getTotalDots(); i++) { const dot = document.createElement('div'); dot.classList.add('carousel-dot'); if (i === 0) dot.classList.add('active'); dot.addEventListener('click', () => goToSlide(i)); dotsContainer.appendChild(dot); } } function updateCarousel() { cards = Array.from(carousel.querySelectorAll('.team-card')); if (!cards.length) return; const gap = 32; const cardWidth = cards[0].offsetWidth + gap; carousel.style.transform = `translateX(-${currentIndex * cardWidth}px)`; Array.from(dotsContainer.querySelectorAll('.carousel-dot')).forEach((d, i) => d.classList.toggle('active', i === currentIndex)); } function goToSlide(index) { currentIndex = Math.min(index, Math.max(0, cards.length - cardsPerView)); updateCarousel(); resetAutoPlay(); } function nextSlide() { const max = Math.max(0, cards.length - cardsPerView); currentIndex = currentIndex >= max ? 0 : currentIndex + 1; updateCarousel(); } function prevSlide() { const max = Math.max(0, cards.length - cardsPerView); currentIndex = currentIndex <= 0 ? max : currentIndex - 1; updateCarousel(); } function startAutoPlay() { clearInterval(autoPlayInterval); autoPlayInterval = setInterval(nextSlide, 4000); } function resetAutoPlay() { clearInterval(autoPlayInterval); startAutoPlay(); } prevBtn && prevBtn.addEventListener('click', () => { prevSlide(); resetAutoPlay(); }); nextBtn && nextBtn.addEventListener('click', () => { nextSlide(); resetAutoPlay(); }); carousel.addEventListener('mouseenter', () => clearInterval(autoPlayInterval)); carousel.addEventListener('mouseleave', startAutoPlay); let resizeTimer; const ro = new ResizeObserver(() => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { updateCardsPerView(); currentIndex = 0; createDots(); updateCarousel(); }, 100); }); ro.observe(carousel.parentElement || document.body); updateCardsPerView(); createDots(); updateCarousel(); startAutoPlay(); // Modal const modalOverlay = document.getElementById('team-modal-overlay'); const modalClose = document.getElementById('team-modal-close'); const modalAvatar = document.getElementById('team-modal-avatar'); const modalName = document.getElementById('team-modal-name'); const modalRole = document.getElementById('team-modal-role'); const modalBio = document.getElementById('team-modal-bio'); const modalStats = document.getElementById('team-modal-stats'); const modalSkills = document.getElementById('team-modal-skills'); const modalSocial = document.getElementById('team-modal-social'); function openMemberModal(index) { const m = members[index]; if (!m) return; // Avatar avec image si dispo const imgSrc = `assets/images/team/${m.id}.png`; modalAvatar.innerHTML = ` ${m.name} ${m.avatar || m.name.charAt(0)} `; modalName.textContent = m.name; modalRole.textContent = m.role; modalBio.textContent = m.bio; modalStats.innerHTML = (m.stats || []).map(s => `
${s.value}
${s.label}
`).join(''); modalSkills.innerHTML = (m.skills || []).map(s => `${s}`).join(''); // Réseaux sociaux depuis le JSON du membre (+ fallback discord global) const socialHtml = typeof window.renderSocialLinks === 'function' ? window.renderSocialLinks(m.social, (window.OK_SOCIAL || {}).discord) : ''; modalSocial.innerHTML = socialHtml; modalOverlay.classList.add('active'); document.body.style.overflow = 'hidden'; } function closeMemberModal() { modalOverlay.classList.remove('active'); document.body.style.overflow = ''; } carousel.querySelectorAll('.team-card').forEach(card => { const openCard = () => { const idx = parseInt(card.dataset.member, 10); openMemberModal(idx); }; card.addEventListener('click', openCard); card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openCard(); } }); }); modalClose && modalClose.addEventListener('click', closeMemberModal); modalOverlay && modalOverlay.addEventListener('click', e => { if (e.target === modalOverlay) closeMemberModal(); }); document.addEventListener('keydown', e => { if (e.key === 'Escape' && modalOverlay && modalOverlay.classList.contains('active')) closeMemberModal(); }); } }); // ═══════════════════════════════════════════════════════════════════════════ // EASTER EGG — top-level (hors DOMContentLoaded pour garantir l'attachement) // ═══════════════════════════════════════════════════════════════════════════ (function () { var KONAMI = ['ArrowUp','ArrowUp','ArrowDown','ArrowDown','ArrowLeft','ArrowRight','ArrowLeft','ArrowRight','b','a']; var progress = 0; // PC : Konami Code — capture:true pour passer avant tout autre listener window.addEventListener('keydown', function(e) { var key = e.key.length === 1 ? e.key.toLowerCase() : e.key; if (key === KONAMI[progress]) { progress++; if (progress === KONAMI.length) { progress = 0; triggerEasterEgg(); } } else { progress = (key === KONAMI[0]) ? 1 : 0; } }, true); // capture phase — reçu avant tout stopPropagation // Mobile : 10 taps sur le logo navbar OU logo footer var tapCount = 0; var tapTimer = null; document.addEventListener('click', function(e) { if (!e.target.closest('.logo') && !e.target.closest('.logo-mini') && !e.target.closest('.omega-symbol')) return; if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0) return; tapCount++; clearTimeout(tapTimer); tapTimer = setTimeout(function() { tapCount = 0; }, 600); if (tapCount >= 10) { tapCount = 0; triggerEasterEgg(); } }); function triggerEasterEgg() { if (document.getElementById('easter-egg-overlay')) return; var egg = document.createElement('div'); egg.id = 'easter-egg-overlay'; egg.setAttribute('role', 'dialog'); egg.setAttribute('aria-modal', 'true'); egg.innerHTML = '
🎮

GG\u00a0! Tu as trouvé l\u2019easter egg\u00a0!

La vraie question c\u2019est\u00a0: combien de temps tu as mis\u00a0?

'; document.body.appendChild(egg); var closeBtn = document.getElementById('easter-egg-close'); closeBtn && closeBtn.focus(); var remove = function() { egg.remove(); }; closeBtn && closeBtn.addEventListener('click', remove); egg.addEventListener('click', function(e) { if (e.target === egg) remove(); }); window.addEventListener('keydown', function esc(e) { if (e.key === 'Escape') { remove(); window.removeEventListener('keydown', esc); } }, { once: true }); } })();