JS Africa GRC

/*================================================== AFRICAGRC PREMIUM JS V2 PARTIE 1 INITIALISATION • HERO • SCROLL • REVEAL ==================================================*/ « use strict »; document.addEventListener(« DOMContentLoaded », () => { const prefersReducedMotion = window.matchMedia(« (prefers-reduced-motion: reduce) »).matches; const body = document.body; /*================================================== AJOUT CLASSE LOADED ==================================================*/ window.addEventListener(« load », () => { body.classList.add(« loaded »); }); /*================================================== SCROLL FLUIDE ==================================================*/ document.querySelectorAll(‘a[href^= »# »]’).forEach(anchor => { anchor.addEventListener(« click », e => { const target = document.querySelector(anchor.getAttribute(« href »)); if(!target) return; e.preventDefault(); target.scrollIntoView({ behavior: »smooth », block: »start » }); }); }); /*================================================== SECTION REVEAL ==================================================*/ if(!prefersReducedMotion){ const revealElements = document.querySelectorAll( « .statement,.focus,.big-quote,.final-cta,.card » ); const revealObserver = new IntersectionObserver( (entries)=>{ entries.forEach(entry=>{ if(entry.isIntersecting){ entry.target.classList.add(« is-visible »); revealObserver.unobserve(entry.target); } }); }, { threshold:.15 } ); revealElements.forEach(el=>{ el.classList.add(« reveal »); revealObserver.observe(el); }); } /*================================================== NAVBAR EFFECT ==================================================*/ const navbar = document.querySelector(« header »); if(navbar){ const updateNavbar = ()=>{ if(window.scrollY>60){ navbar.classList.add(« scrolled »); }else{ navbar.classList.remove(« scrolled »); } }; updateNavbar(); window.addEventListener( « scroll », updateNavbar, {passive:true} ); } /*================================================== HERO PARALLAX ==================================================*/ const hero = document.querySelector(« .hero »); if(hero && !prefersReducedMotion){ let currentX=0; let currentY=0; let targetX=0; let targetY=0; hero.addEventListener(« mousemove »,e=>{ const rect=hero.getBoundingClientRect(); targetX=(e.clientX-rect.left)/rect.width-.5; targetY=(e.clientY-rect.top)/rect.height-.5; }); const animateHero=()=>{ currentX+=(targetX-currentX)*.06; currentY+=(targetY-currentY)*.06; hero.style.backgroundPosition= `${50+currentX*3}% ${50+currentY*3}%`; requestAnimationFrame(animateHero); }; animateHero(); } /*================================================== ACTIVE BUTTON ==================================================*/ document.querySelectorAll(« .btn »).forEach(btn=>{ btn.addEventListener(« mouseenter »,()=>{ btn.classList.add(« hovering »); }); btn.addEventListener(« mouseleave »,()=>{ btn.classList.remove(« hovering »); }); }); /*================================================== SECTION ACTIVE ==================================================*/ const sections=document.querySelectorAll(« section[id] »); if(sections.length){ const sectionObserver=new IntersectionObserver( entries=>{ entries.forEach(entry=>{ if(entry.isIntersecting){ document.body.setAttribute( « data-section », entry.target.id ); } }); }, { threshold:.45 } ); sections.forEach(section=>{ sectionObserver.observe(section); }); } }); /*================================================== PARTIE 2 CARDS • BUTTONS • COUNTERS • INTERACTIONS ==================================================*/ /*================================================== CARDS 3D EFFECT ==================================================*/ if(!prefersReducedMotion){ const cards=document.querySelectorAll(« .card »); cards.forEach(card=>{ let frame; card.addEventListener(« mousemove »,(e)=>{ cancelAnimationFrame(frame); frame=requestAnimationFrame(()=>{ const rect=card.getBoundingClientRect(); const x=e.clientX-rect.left; const y=e.clientY-rect.top; const rotateY=((x/rect.width)-0.5)*8; const rotateX=((y/rect.height)-0.5)*-8; card.style.transform=` perspective(900px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) translateY(-8px) `; }); }); card.addEventListener(« mouseleave »,()=>{ card.style.transform= » »; }); }); } /*================================================== BUTTON RIPPLE ==================================================*/ document.querySelectorAll(« .btn »).forEach(button=>{ button.addEventListener(« click »,function(e){ const circle=document.createElement(« span »); const diameter=Math.max(this.clientWidth,this.clientHeight); const radius=diameter/2; circle.style.width=circle.style.height=`${diameter}px`; circle.style.left=`${e.clientX-this.getBoundingClientRect().left-radius}px`; circle.style.top=`${e.clientY-this.getBoundingClientRect().top-radius}px`; circle.classList.add(« ripple »); const ripple=this.querySelector(« .ripple »); if(ripple){ ripple.remove(); } this.appendChild(circle); }); }); /*================================================== COUNTERS ==================================================*/ const counters=document.querySelectorAll(« [data-counter] »); if(counters.length){ const counterObserver=new IntersectionObserver(entries=>{ entries.forEach(entry=>{ if(!entry.isIntersecting) return; const counter=entry.target; const target=parseInt(counter.dataset.counter); const duration=1800; const start=performance.now(); const update=(time)=>{ const progress=Math.min((time-start)/duration,1); counter.textContent=Math.floor(progress*target); if(progress<1){ requestAnimationFrame(update); }else{ counter.textContent=target; } }; requestAnimationFrame(update); counterObserver.unobserve(counter); }); },{threshold:.6}); counters.forEach(counter=>{ counterObserver.observe(counter); }); } /*================================================== CARD ICON ANIMATION ==================================================*/ document.querySelectorAll(« .card i,.card svg »).forEach(icon=>{ icon.addEventListener(« mouseenter »,()=>{ icon.style.transform= »scale(1.15) rotate(6deg) »; }); icon.addEventListener(« mouseleave »,()=>{ icon.style.transform= » »; }); }); /*================================================== CTA BUTTON PULSE ==================================================*/ const ctaButtons=document.querySelectorAll(« .btn-primary »); setInterval(()=>{ ctaButtons.forEach(btn=>{ btn.classList.add(« pulse »); setTimeout(()=>{ btn.classList.remove(« pulse »); },1200); }); },15000); /*================================================== FOCUS CARDS ==================================================*/ document.querySelectorAll(« .focus .card »).forEach(card=>{ card.addEventListener(« mouseenter »,()=>{ card.classList.add(« active-card »); }); card.addEventListener(« mouseleave »,()=>{ card.classList.remove(« active-card »); }); }); /*================================================== SCROLL PROGRESS ==================================================*/ const progress=document.createElement(« div »); progress.id= »scroll-progress »; document.body.appendChild(progress); window.addEventListener(« scroll »,()=>{ const total=document.documentElement.scrollHeight-window.innerHeight; const width=(window.scrollY/total)*100; progress.style.width=width+ »% »; },{passive:true}); /*================================================== PARTIE 3 PERFORMANCE • CTA • UTILITIES ==================================================*/ /*================================================== BACK TO TOP ==================================================*/ const backToTop=document.createElement(« button »); backToTop.id= »backToTop »; backToTop.innerHTML= »↑ »; document.body.appendChild(backToTop); window.addEventListener(« scroll »,()=>{ if(window.scrollY>700){ backToTop.classList.add(« show »); }else{ backToTop.classList.remove(« show »); } },{passive:true}); backToTop.addEventListener(« click »,()=>{ window.scrollTo({ top:0, behavior: »smooth » }); }); /*================================================== CTA OBSERVER ==================================================*/ const cta=document.querySelector(« .final-cta »); if(cta){ const ctaObserver=new IntersectionObserver(entries=>{ entries.forEach(entry=>{ if(entry.isIntersecting){ cta.classList.add(« visible »); } }); },{threshold:.35}); ctaObserver.observe(cta); } /*================================================== IMAGE PRELOAD ==================================================*/ document.querySelectorAll(« img »).forEach(img=>{ if(img.complete) return; img.loading= »lazy »; }); /*================================================== REDUCED MOTION ==================================================*/ if(prefersReducedMotion){ document.documentElement.classList.add(« reduced-motion »); } /*================================================== KEYBOARD ACCESSIBILITY ==================================================*/ document.addEventListener(« keyup »,(e)=>{ if(e.key=== »Tab »){ document.body.classList.add(« keyboard-user »); } }); /*================================================== WINDOW RESIZE ==================================================*/ let resizeTimer; window.addEventListener(« resize »,()=>{ clearTimeout(resizeTimer); resizeTimer=setTimeout(()=>{ document.body.classList.add(« resized »); setTimeout(()=>{ document.body.classList.remove(« resized »); },400); },200); }); /*================================================== CONSOLE SIGNATURE ==================================================*/ console.log( « %cAfricaGRC Premium UI », « color:#22C55E;font-size:18px;font-weight:bold; » ); console.log( « %cPowered by SIL2 », « color:#0B5ED7;font-size:14px; » ); });
Retour en haut