2026-04-05 20:49:52 +02:00
// Home page specific JS: team carousel + modal + portfolio preview + partners + konami
2026-02-06 18:36:03 +01:00
document . addEventListener ( 'DOMContentLoaded' , ( ) => {
2026-04-05 20:49:52 +02:00
// ── 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 imgSrc = ( p . images || '' ) + '1.png' ;
return `
< div class = "portfolio-item reveal${delayClasses[i]}" data - project - id = "${p.id}" role = "button" tabindex = "0" aria - label = "Voir le projet ${p.title}" >
< div class = "portfolio-bg" style = "background: ${p.gradient};" >
< img src = "${imgSrc}" alt = "${p.title}" class = "portfolio-cover-img" onerror = "this.style.display='none'" >
< / d i v >
< div class = "portfolio-content" >
< span class = "portfolio-tag" > $ { categoryLabels [ p . category ] || p . category } < / s p a n >
< h4 > $ { p . title } < / h 4 >
< p > $ { p . desc . substring ( 0 , 80 ) } $ { p . desc . length > 80 ? '…' : '' } < / p >
< / d i v >
< / d i v >
` ;
} ) . 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 : attend que openProjectModal soit défini par realisations.js
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 ) ;
}
} ) ;
} )
. 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 = ` assets/images/partners/ ${ p . name } .png ` ;
return `
< div class = "partner-slide" data - partner - idx = "${i % partners.length}" role = "button" tabindex = "0" aria - label = "Voir ${p.displayName}" >
< div class = "partner-slide-inner" >
< img src = "${imgSrc}" alt = "${p.displayName}" class = "partner-logo-img"
onerror = "this.style.display='none';this.nextElementSibling.style.display='flex';" >
< div class = "partner-logo-fallback" style = "background:${p.gradient};display:none;" >
$ { p . displayName . substring ( 0 , 2 ) . toUpperCase ( ) }
< / d i v >
< span class = "partner-slide-name" > $ { p . displayName } < / s p a n >
< / d i v >
< / d i v >
` ;
} ) . 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 = ` assets/images/partners/ ${ p . name } .png ` ;
document . getElementById ( 'partner-modal-logo-img' ) . src = imgSrc ;
document . getElementById ( 'partner-modal-logo-img' ) . alt = p . displayName ;
document . getElementById ( 'partner-modal-logo-img' ) . onerror = function ( ) {
this . style . display = 'none' ;
document . getElementById ( 'partner-modal-logo-fallback' ) . style . display = 'flex' ;
document . getElementById ( 'partner-modal-logo-fallback' ) . style . background = p . gradient ;
document . getElementById ( 'partner-modal-logo-fallback' ) . textContent = p . displayName . substring ( 0 , 2 ) . toUpperCase ( ) ;
} ;
document . getElementById ( 'partner-modal-logo-fallback' ) . style . display = 'none' ;
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 => ` <span class="team-modal-skill"> ${ s } </span> ` ) . join ( '' ) ;
document . getElementById ( 'partner-modal-stats' ) . innerHTML = ( p . stats || [ ] ) . map ( s => `
< div class = "team-modal-stat" >
< div class = "team-modal-stat-value" > $ { s . value } < / d i v >
< div class = "team-modal-stat-label" > $ { s . label } < / d i v >
< / d i v >
` ).join('');
const links = document . getElementById ( 'partner-modal-links' ) ;
links . innerHTML = '' ;
if ( p . url && p . url !== '#' ) {
links . innerHTML += ` <a href=" ${ p . url } " target="_blank" rel="noopener noreferrer" class="btn btn-secondary"><i data-feather="external-link"></i> Site web</a> ` ;
}
if ( p . discord && p . discord !== '#' ) {
links . innerHTML += ` <a href=" ${ p . discord } " target="_blank" rel="noopener noreferrer" class="btn btn-primary">
< svg viewBox = "0 0 24 24" fill = "currentColor" width = "16" height = "16" > < path d = "M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03z" / > < / s v g >
Discord
< / a > ` ;
}
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 ─────────────────────────────────────────────────────────────
2026-02-06 18:36:03 +01:00
fetch ( 'assets/data/team.json' )
. then ( res => res . json ( ) )
2026-04-05 20:49:52 +02:00
. then ( members => { initTeamSection ( members ) ; } )
. catch ( err => { console . error ( 'Failed to load team data' , err ) ; } ) ;
2026-02-06 18:36:03 +01:00
2026-04-05 20:49:52 +02:00
// Konami + easter egg → définis au niveau top-level du script (voir bas de fichier)
// ── initTeamSection ──────────────────────────────────────────────────────────
2026-02-06 18:36:03 +01:00
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' ) ;
2026-04-05 20:49:52 +02:00
carousel . innerHTML = members . map ( ( m , i ) => {
const imgSrc = ` assets/images/team/ ${ m . id } .png ` ;
return `
< div class = "team-card" data - member = "${i}" role = "button" tabindex = "0" aria - label = "Voir le profil de ${m.name}" >
< div class = "team-avatar" >
< div class = "team-avatar-inner" id = "team-avatar-inner-${i}" >
< img src = "${imgSrc}" alt = "${m.name}" class = "team-avatar-photo"
onerror = "this.style.display='none';document.getElementById('team-avatar-fallback-${i}').style.display='flex';" >
< span class = "team-avatar-fallback" id = "team-avatar-fallback-${i}" style = "display:none;" > $ { m . avatar } < / s p a n >
< / d i v >
< / d i v >
< h4 > $ { m . name } < / h 4 >
< p class = "team-role" > $ { m . role } < / p >
< p class = "team-desc" > $ { ( m . bio || '' ) . split ( '.' ) . shift ( ) } . < / p >
2026-02-06 18:36:03 +01:00
< / d i v >
2026-04-05 20:49:52 +02:00
` ;
} ) . join ( '' ) ;
let cards = Array . from ( carousel . querySelectorAll ( '.team-card' ) ) ;
2026-02-06 18:36:03 +01:00
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 ;
}
2026-04-05 20:49:52 +02:00
function getTotalDots ( ) { return Math . max ( 1 , cards . length - cardsPerView + 1 ) ; }
2026-02-06 18:36:03 +01:00
function createDots ( ) {
dotsContainer . innerHTML = '' ;
2026-04-05 20:49:52 +02:00
for ( let i = 0 ; i < getTotalDots ( ) ; i ++ ) {
2026-02-06 18:36:03 +01:00
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 ( ) {
2026-04-05 20:49:52 +02:00
cards = Array . from ( carousel . querySelectorAll ( '.team-card' ) ) ;
if ( ! cards . length ) return ;
const gap = 32 ;
2026-02-06 18:36:03 +01:00
const cardWidth = cards [ 0 ] . offsetWidth + gap ;
carousel . style . transform = ` translateX(- ${ currentIndex * cardWidth } px) ` ;
2026-04-05 20:49:52 +02:00
Array . from ( dotsContainer . querySelectorAll ( '.carousel-dot' ) ) . forEach ( ( d , i ) => d . classList . toggle ( 'active' , i === currentIndex ) ) ;
2026-02-06 18:36:03 +01:00
}
2026-04-05 20:49:52 +02:00
function goToSlide ( index ) {
currentIndex = Math . min ( index , Math . max ( 0 , cards . length - cardsPerView ) ) ;
updateCarousel ( ) ; resetAutoPlay ( ) ;
2026-02-06 18:36:03 +01:00
}
function nextSlide ( ) {
2026-04-05 20:49:52 +02:00
const max = Math . max ( 0 , cards . length - cardsPerView ) ;
currentIndex = currentIndex >= max ? 0 : currentIndex + 1 ;
2026-02-06 18:36:03 +01:00
updateCarousel ( ) ;
}
function prevSlide ( ) {
2026-04-05 20:49:52 +02:00
const max = Math . max ( 0 , cards . length - cardsPerView ) ;
currentIndex = currentIndex <= 0 ? max : currentIndex - 1 ;
2026-02-06 18:36:03 +01:00
updateCarousel ( ) ;
}
2026-04-05 20:49:52 +02:00
function startAutoPlay ( ) { clearInterval ( autoPlayInterval ) ; autoPlayInterval = setInterval ( nextSlide , 4000 ) ; }
function resetAutoPlay ( ) { clearInterval ( autoPlayInterval ) ; startAutoPlay ( ) ; }
2026-02-06 18:36:03 +01:00
prevBtn && prevBtn . addEventListener ( 'click' , ( ) => { prevSlide ( ) ; resetAutoPlay ( ) ; } ) ;
nextBtn && nextBtn . addEventListener ( 'click' , ( ) => { nextSlide ( ) ; resetAutoPlay ( ) ; } ) ;
carousel . addEventListener ( 'mouseenter' , ( ) => clearInterval ( autoPlayInterval ) ) ;
carousel . addEventListener ( 'mouseleave' , startAutoPlay ) ;
2026-04-05 20:49:52 +02:00
let resizeTimer ;
const ro = new ResizeObserver ( ( ) => {
clearTimeout ( resizeTimer ) ;
resizeTimer = setTimeout ( ( ) => { updateCardsPerView ( ) ; currentIndex = 0 ; createDots ( ) ; updateCarousel ( ) ; } , 100 ) ;
2026-02-06 18:36:03 +01:00
} ) ;
2026-04-05 20:49:52 +02:00
ro . observe ( carousel . parentElement || document . body ) ;
updateCardsPerView ( ) ; createDots ( ) ; updateCarousel ( ) ; startAutoPlay ( ) ;
2026-02-06 18:36:03 +01:00
2026-04-05 20:49:52 +02:00
// Modal
2026-02-06 18:36:03 +01:00
const modalOverlay = document . getElementById ( 'team-modal-overlay' ) ;
const modalClose = document . getElementById ( 'team-modal-close' ) ;
const modalAvatar = document . getElementById ( 'modal-avatar' ) ;
const modalName = document . getElementById ( 'modal-name' ) ;
const modalRole = document . getElementById ( 'modal-role' ) ;
const modalBio = document . getElementById ( 'modal-bio' ) ;
const modalStats = document . getElementById ( 'modal-stats' ) ;
const modalSkills = document . getElementById ( 'modal-skills' ) ;
const modalSocial = document . getElementById ( 'modal-social' ) ;
function openMemberModal ( index ) {
2026-04-05 20:49:52 +02:00
const m = members [ index ] ;
if ( ! m ) return ;
// Avatar avec image si dispo
const imgSrc = ` assets/images/team/ ${ m . id } .png ` ;
modalAvatar . innerHTML = `
< img src = "${imgSrc}" alt = "${m.name}" class = "team-modal-avatar-photo"
onerror = "this.style.display='none';this.nextElementSibling.style.display='flex';" >
< span class = "team-modal-avatar-fallback" > $ { m . avatar || m . name . charAt ( 0 ) } < / s p a n >
` ;
modalName . textContent = m . name ;
modalRole . textContent = m . role ;
modalBio . textContent = m . bio ;
modalStats . innerHTML = ( m . stats || [ ] ) . map ( s => `
2026-02-06 18:36:03 +01:00
< div class = "team-modal-stat" >
2026-04-05 20:49:52 +02:00
< div class = "team-modal-stat-value" > $ { s . value } < / d i v >
< div class = "team-modal-stat-label" > $ { s . label } < / d i v >
2026-02-06 18:36:03 +01:00
< / d i v >
` ).join('');
2026-04-05 20:49:52 +02:00
modalSkills . innerHTML = ( m . skills || [ ] ) . map ( s => ` <span class="team-modal-skill"> ${ s } </span> ` ) . join ( '' ) ;
2026-02-06 18:36:03 +01:00
modalSocial . innerHTML = `
2026-04-05 20:49:52 +02:00
< a href = "https://discord.gg/FwvNkvu5dy" target = "_blank" rel = "noopener noreferrer" aria - label = "Discord" >
< svg viewBox = "0 0 24 24" fill = "currentColor" width = "18" height = "18" > < path d = "M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" / > < / s v g >
< / a >
< a href = "#" target = "_blank" rel = "noopener noreferrer" aria - label = "X (Twitter)" >
< svg viewBox = "0 0 24 24" fill = "currentColor" width = "18" height = "18" > < path d = "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.259 5.631 5.905-5.631zm-1.161 17.52h1.833L7.084 4.126H5.117z" / > < / s v g >
< / a >
2026-02-06 18:36:03 +01:00
` ;
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 => {
2026-04-05 20:49:52 +02:00
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 ( ) ; } } ) ;
2026-02-06 18:36:03 +01:00
} ) ;
modalClose && modalClose . addEventListener ( 'click' , closeMemberModal ) ;
2026-04-05 20:49:52 +02:00
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 = '<div class="easter-egg-box"><div class="easter-egg-icon">🎮</div><h2>GG\u00a0! Tu as trouvé l\u2019easter egg\u00a0!</h2><p>La vraie question c\u2019est\u00a0: combien de temps tu as mis\u00a0?</p><button id="easter-egg-close" class="btn btn-primary">Fermer</button></div>' ;
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 } ) ;
2026-02-06 18:36:03 +01:00
}
2026-04-05 20:49:52 +02:00
} ) ( ) ;