plexi09.me/js/main.js
2026-02-23 00:01:42 +01:00

166 lines
7.1 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
// ── 1. LENIS SMOOTH SCROLL ────────────────────────────────────────
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smooth: true,
mouseMultiplier: 1,
smoothTouch: false,
touchMultiplier: 2,
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
// ── 2. GSAP PLUGINS ───────────────────────────────────────────────
gsap.registerPlugin(ScrollTrigger);
// ── 3. PRELOADER ──────────────────────────────────────────────────
gsap.set('.hero-title, .hero-subtitle', { opacity: 0 });
gsap.timeline()
.to('#loader-text', { y: 0, duration: 1, ease: 'power4.out', delay: 0.2 })
.to('#loader-text', { y: '-100%', duration: 1, ease: 'power4.in', delay: 0.5 })
.to('#preloader', { y: '-100%', duration: 1, ease: 'power4.inOut' }, '-=0.5')
.to('.hero-title', { y: 0, opacity: 1, duration: 1.5, stagger: 0.15, ease: 'power4.out' }, '-=0.4')
.to('.hero-subtitle', { y: 0, opacity: 1, duration: 1.2, stagger: 0.12, ease: 'power3.out' }, '-=1.2');
// ── 4. CUSTOM CURSOR ──────────────────────────────────────────────
if (window.innerWidth >= 768) {
const cursor = document.getElementById('cursor');
const cursorDot = document.getElementById('cursor-dot');
let mouseX = 0, mouseY = 0, cursorX = 0, cursorY = 0;
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
gsap.to(cursorDot, { x: mouseX, y: mouseY, duration: 0 });
});
gsap.ticker.add(() => {
cursorX += (mouseX - cursorX) * 0.2;
cursorY += (mouseY - cursorY) * 0.2;
gsap.set(cursor, { x: cursorX, y: cursorY });
});
document.querySelectorAll('.hover-target').forEach(target => {
target.addEventListener('mouseenter', () => {
gsap.to(cursor, { scale: 2.5, backgroundColor: 'rgba(255,255,255,1)', duration: 0.3 });
gsap.to(cursorDot, { opacity: 0, duration: 0.3 });
});
target.addEventListener('mouseleave', () => {
gsap.to(cursor, { scale: 1, backgroundColor: 'transparent', duration: 0.3 });
gsap.to(cursorDot, { opacity: 1, duration: 0.3 });
});
});
}
// ── 5. SCROLL PROGRESS BAR ────────────────────────────────────────
const progressBar = document.getElementById('progress-bar');
lenis.on('scroll', ({ progress }) => {
if (progressBar) progressBar.style.width = `${progress * 100}%`;
});
// ── 6. ACTIVE NAV LINK ────────────────────────────────────────────
const navLinks = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('section[id]');
const sectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinks.forEach(link => {
link.classList.toggle('active', link.dataset.section === id);
});
}
});
}, { rootMargin: '-40% 0px -40% 0px', threshold: 0 });
sections.forEach(s => sectionObserver.observe(s));
// ── 7. MOBILE MENU ────────────────────────────────────────────────
const hamburger = document.getElementById('hamburger');
const mobileOverlay = document.getElementById('mobile-overlay');
if (hamburger && mobileOverlay) {
hamburger.addEventListener('click', () => {
const isOpen = hamburger.classList.contains('open');
hamburger.classList.toggle('open');
mobileOverlay.classList.toggle('open');
isOpen ? lenis.start() : lenis.stop();
});
document.querySelectorAll('.mobile-nav-link').forEach(link => {
link.addEventListener('click', () => {
hamburger.classList.remove('open');
mobileOverlay.classList.remove('open');
lenis.start();
});
});
}
// ── 8. REVEAL TEXT ANIMATIONS ─────────────────────────────────────
gsap.utils.toArray('.reveal-text').forEach(el => {
gsap.from(el, {
scrollTrigger: { trigger: el, start: 'top 85%' },
y: 60,
opacity: 0,
duration: 1.2,
ease: 'power3.out',
});
});
// ── 9. CV STAGGERED ANIMATIONS ────────────────────────────────────
gsap.utils.toArray('.cv-column').forEach(col => {
const items = col.querySelectorAll('.group');
if (!items.length) return;
gsap.from(items, {
scrollTrigger: { trigger: col, start: 'top 82%' },
y: 40,
opacity: 0,
duration: 0.85,
stagger: 0.18,
ease: 'power3.out',
});
});
// ── 10. PARALLAX IMAGES ───────────────────────────────────────────
gsap.utils.toArray('.parallax-img').forEach(img => {
gsap.to(img, {
scrollTrigger: {
trigger: img.parentElement,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
y: 50,
ease: 'none',
});
});
// ── 11. DRAGGABLE GALLERY ─────────────────────────────────────────
const gallery = document.getElementById('gallery');
if (gallery) {
let isDown = false, startX, scrollLeft;
gallery.addEventListener('mousedown', (e) => {
isDown = true;
startX = e.pageX - gallery.offsetLeft;
scrollLeft = gallery.scrollLeft;
});
gallery.addEventListener('mouseleave', () => { isDown = false; });
gallery.addEventListener('mouseup', () => { isDown = false; });
gallery.addEventListener('mousemove', (e) => {
if (!isDown) return;
e.preventDefault();
gallery.scrollLeft = scrollLeft - (e.pageX - gallery.offsetLeft - startX) * 2;
});
}
});