const { useState, useEffect } = React;
function App() {
const [activeSection, setActiveSection] = useState('');
useEffect(() => {
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReducedMotion) return;
gsap.registerPlugin(ScrollTrigger);
// ── Page load sequence + hero word-split ──
const wordSpans = document.querySelectorAll('.h1-word');
wordSpans.forEach(s => s.style.willChange = 'transform');
gsap.set('nav', { opacity: 0, y: -16 });
gsap.set('.hero-eyebrow', { opacity: 0, y: 12 });
gsap.set(wordSpans, { opacity: 0, y: 50 });
gsap.set('.hero-bottom', { opacity: 0, y: 20 });
const tl = gsap.timeline({ delay: 0.1 });
tl.to('nav', { opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' }, 0.0)
.to('.hero-eyebrow', { opacity: 1, y: 0, duration: 0.4, ease: 'power2.out' }, 0.2)
.to(wordSpans, { opacity: 1, y: 0, duration: 0.7, ease: 'power3.out', stagger: 0.07,
onComplete: () => wordSpans.forEach(s => s.style.willChange = 'auto') }, 0.4)
.to('.hero-bottom', { opacity: 1, y: 0, duration: 0.5, ease: 'power2.out' }, 1.0);
// ── Sectiekoppen: subtiele reveal ──
gsap.utils.toArray('section .section-label, section h2, section .section-intro').forEach(el => {
if (el.closest('.hero')) return;
gsap.from(el, {
y: 24, opacity: 0, duration: 0.6, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: el, start: 'top 88%', once: true }
});
});
// ── Calculator reveal ──
gsap.from('.calculator', {
y: 40, opacity: 0, duration: 0.7, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: '.calculator', start: 'top 85%', once: true }
});
// ── Contactform reveal ──
gsap.from('.contact-form', {
y: 30, opacity: 0, duration: 0.6, delay: 0.15, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: '.cta-section', start: 'top 75%', once: true }
});
// ── Guarantees ──
gsap.from('.guarantee-item', {
y: 30, opacity: 0, duration: 0.5, ease: 'power2.out', stagger: 0.1,
immediateRender: false,
scrollTrigger: { trigger: '.guarantees-grid', start: 'top 85%', once: true }
});
gsap.from('.guarantee-ip', {
y: 30, opacity: 0, duration: 0.6, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: '.guarantee-ip', start: 'top 88%', once: true }
});
// ── Value cards ──
gsap.from('.value-card', {
y: 30, opacity: 0, duration: 0.5, ease: 'power2.out', stagger: 0.08,
immediateRender: false,
scrollTrigger: { trigger: '.values-grid', start: 'top 85%', once: true }
});
// ── Process steps ──
gsap.from('.step', {
x: -20, opacity: 0, duration: 0.4, ease: 'power2.out', stagger: 0.1,
immediateRender: false,
scrollTrigger: { trigger: '.process-steps', start: 'top 85%', once: true }
});
// ── Service cards ──
gsap.from('.service-card', {
y: 20, opacity: 0, duration: 0.4, ease: 'power2.out', stagger: 0.07,
immediateRender: false,
scrollTrigger: { trigger: '.service-cards', start: 'top 85%', once: true }
});
// ── Case card ──
gsap.from('.case-info', {
x: -40, opacity: 0, duration: 0.7, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: '.case-card', start: 'top 80%', once: true }
});
gsap.from('.mockup', {
x: 40, opacity: 0, duration: 0.7, delay: 0.15, ease: 'power2.out',
immediateRender: false,
scrollTrigger: { trigger: '.case-card', start: 'top 80%', once: true }
});
// ── Stat counter ──
ScrollTrigger.create({
trigger: '.case-stats',
start: 'top 80%',
once: true,
onEnter: () => {
const el = document.getElementById('stat-cloud');
if (!el) return;
const obj = { val: 0 };
anime({ targets: obj, val: 100, duration: 1200, easing: 'easeOutQuart',
update: () => { el.textContent = Math.round(obj.val) + '%'; }
});
}
});
// ── Mockup parallax ──
gsap.to('.mockup', {
yPercent: -6, ease: 'none',
scrollTrigger: { trigger: '.case-card', scrub: 2 }
});
// ── Chart bars ──
const bars = document.querySelectorAll('.bar');
ScrollTrigger.create({
trigger: '.mock-chart',
start: 'top 85%',
once: true,
onEnter: () => {
bars.forEach((bar, i) => {
anime({ targets: bar, height: bar.dataset.height, duration: 600,
delay: i * 60, easing: 'easeOutElastic(1, .8)' });
});
}
});
// ── Magnetic buttons ──
document.querySelectorAll('.magnetic').forEach(btn => {
btn.addEventListener('mousemove', (e) => {
const r = btn.getBoundingClientRect();
const dx = (e.clientX - (r.left + r.width / 2)) / (r.width / 2);
const dy = (e.clientY - (r.top + r.height / 2)) / (r.height / 2);
gsap.to(btn, { x: dx * 6, y: dy * 6, duration: 0.3, ease: 'power2.out' });
});
btn.addEventListener('mouseleave', () => {
gsap.to(btn, { x: 0, y: 0, duration: 0.5, ease: 'elastic.out(1, 0.3)' });
});
});
// ── Nav active indicator ──
['#services', '#cases', '#pricing', '#contact'].forEach(id => {
const el = document.querySelector(id);
if (!el) return;
ScrollTrigger.create({
trigger: el,
start: 'top 60%',
end: 'bottom 60%',
onEnter: () => setActiveSection(id),
onEnterBack: () => setActiveSection(id),
onLeave: () => setActiveSection(s => s === id ? '' : s),
onLeaveBack: () => setActiveSection(s => s === id ? '' : s),
});
});
requestAnimationFrame(() => ScrollTrigger.refresh());
return () => {
ScrollTrigger.getAll().forEach(t => t.kill());
};
}, []);
return (
<>
>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();