Added particle effects to the header

This commit is contained in:
Plexi09 2026-02-23 00:03:37 +01:00
parent fb253d9c6d
commit 3f1ec3056d
Signed by: Plexi09
GPG key ID: 20D439A69163544A

View file

@ -29,9 +29,99 @@ document.addEventListener('DOMContentLoaded', () => {
.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. CANVAS PARTICLES ───────────────────────────────────────────
const heroCanvas = document.getElementById('hero-canvas');
if (heroCanvas) {
const pCtx = heroCanvas.getContext('2d');
const heroSec = document.getElementById('home');
// Track mouse in viewport coords (works on all devices)
const pMouse = { x: null, y: null };
document.addEventListener('mousemove', (e) => { pMouse.x = e.clientX; pMouse.y = e.clientY; });
document.addEventListener('mouseleave', () => { pMouse.x = null; pMouse.y = null; });
// ── 4. CUSTOM CURSOR ──────────────────────────────────────────────
function resizeCanvas() {
heroCanvas.width = heroSec.offsetWidth;
heroCanvas.height = heroSec.offsetHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const PARTICLE_COUNT = 90;
const P2P_DIST = 130; // particle ↔ particle connection radius
const CUR_DIST = 200; // cursor ↔ particle connection radius
class Particle {
constructor() { this.init(); }
init() {
this.x = Math.random() * heroCanvas.width;
this.y = Math.random() * heroCanvas.height;
this.vx = (Math.random() - 0.5) * 0.45;
this.vy = (Math.random() - 0.5) * 0.45;
this.radius = Math.random() * 1.4 + 0.4;
this.alpha = Math.random() * 0.45 + 0.2;
}
update() {
this.x += this.vx;
this.y += this.vy;
if (this.x < 0 || this.x > heroCanvas.width) this.vx *= -1;
if (this.y < 0 || this.y > heroCanvas.height) this.vy *= -1;
}
draw() {
pCtx.beginPath();
pCtx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
pCtx.fillStyle = `rgba(255,255,255,${this.alpha})`;
pCtx.fill();
}
}
const particles = Array.from({ length: PARTICLE_COUNT }, () => new Particle());
function line(x1, y1, x2, y2, alpha) {
pCtx.beginPath();
pCtx.moveTo(x1, y1);
pCtx.lineTo(x2, y2);
pCtx.strokeStyle = `rgba(255,255,255,${alpha})`;
pCtx.lineWidth = 0.6;
pCtx.stroke();
}
function animateParticles() {
pCtx.clearRect(0, 0, heroCanvas.width, heroCanvas.height);
// Cursor position relative to the hero section
const rect = heroSec.getBoundingClientRect();
const cx = pMouse.x !== null ? pMouse.x - rect.left : null;
const cy = pMouse.y !== null ? pMouse.y - rect.top : null;
for (let i = 0; i < particles.length; i++) {
const p = particles[i];
p.update();
p.draw();
// Particle ↔ particle
for (let j = i + 1; j < particles.length; j++) {
const q = particles[j];
const dx = p.x - q.x, dy = p.y - q.y;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < P2P_DIST) line(p.x, p.y, q.x, q.y, (1 - d / P2P_DIST) * 0.12);
}
// Cursor ↔ particle
if (cx !== null) {
const dx = p.x - cx, dy = p.y - cy;
const d = Math.sqrt(dx * dx + dy * dy);
if (d < CUR_DIST) line(p.x, p.y, cx, cy, (1 - d / CUR_DIST) * 0.7);
}
}
requestAnimationFrame(animateParticles);
}
animateParticles();
}
// ── 5. CUSTOM CURSOR ──────────────────────────────────────────────
if (window.innerWidth >= 768) {
const cursor = document.getElementById('cursor');
const cursorDot = document.getElementById('cursor-dot');
@ -61,13 +151,13 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// ── 5. SCROLL PROGRESS BAR ────────────────────────────────────────
// ── 6. SCROLL PROGRESS BAR ────────────────────────────────────────
const progressBar = document.getElementById('progress-bar');
lenis.on('scroll', ({ progress }) => {
if (progressBar) progressBar.style.width = `${progress * 100}%`;
});
// ── 6. ACTIVE NAV LINK ────────────────────────────────────────────
// ── 7. ACTIVE NAV LINK ────────────────────────────────────────────
const navLinks = document.querySelectorAll('.nav-link');
const sections = document.querySelectorAll('section[id]');
@ -84,7 +174,7 @@ document.addEventListener('DOMContentLoaded', () => {
sections.forEach(s => sectionObserver.observe(s));
// ── 7. MOBILE MENU ────────────────────────────────────────────────
// ── 8. MOBILE MENU ────────────────────────────────────────────────
const hamburger = document.getElementById('hamburger');
const mobileOverlay = document.getElementById('mobile-overlay');
@ -105,7 +195,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// ── 8. REVEAL TEXT ANIMATIONS ─────────────────────────────────────
// ── 9. REVEAL TEXT ANIMATIONS ─────────────────────────────────────
gsap.utils.toArray('.reveal-text').forEach(el => {
gsap.from(el, {
scrollTrigger: { trigger: el, start: 'top 85%' },
@ -116,7 +206,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
// ── 9. CV STAGGERED ANIMATIONS ────────────────────────────────────
// ── 10. CV STAGGERED ANIMATIONS ────────────────────────────────────
gsap.utils.toArray('.cv-column').forEach(col => {
const items = col.querySelectorAll('.group');
if (!items.length) return;
@ -130,7 +220,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
// ── 10. PARALLAX IMAGES ───────────────────────────────────────────
// ── 11. PARALLAX IMAGES ───────────────────────────────────────────
gsap.utils.toArray('.parallax-img').forEach(img => {
gsap.to(img, {
scrollTrigger: {
@ -144,7 +234,7 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
// ── 11. DRAGGABLE GALLERY ─────────────────────────────────────────
// ── 12. DRAGGABLE GALLERY ─────────────────────────────────────────
const gallery = document.getElementById('gallery');
if (gallery) {
let isDown = false, startX, scrollLeft;