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; »
);
});