portfolio / script.js
nehal2006's picture
Upload folder using huggingface_hub
d7280ef verified
Raw
History Blame Contribute Delete
8.71 kB
// ── PARTICLE CANVAS (must be first) ──
const canvas = document.getElementById('bgCanvas');
const ctx = canvas.getContext('2d');
let W, H, mouseX = 0, mouseY = 0;
const particles = [];
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
resize();
window.addEventListener('resize', resize);
// ── THEME TOGGLE ──
const themeToggle = document.getElementById('themeToggle');
function updateParticleColors() {
const s = getComputedStyle(document.body);
const dot = s.getPropertyValue('--canvas-dot').trim();
const line = s.getPropertyValue('--canvas-line').trim();
particles.forEach(p => {
p.accentColor = line || '124,106,255';
p.baseColor = dot || '255,255,255';
p.color = Math.random() > 0.7 ? p.accentColor : p.baseColor;
});
}
function setTheme(light) {
document.documentElement.classList.toggle('light', light);
document.body.classList.toggle('light', light);
localStorage.setItem('theme', light ? 'light' : 'dark');
updateParticleColors();
}
const saved = localStorage.getItem('theme');
const prefersLight = window.matchMedia('(prefers-color-scheme: light)').matches;
if (saved === 'light' || (!saved && prefersLight)) setTheme(true);
themeToggle.addEventListener('click', () => setTheme(!document.body.classList.contains('light')));
// ── PARTICLES ──
class Particle {
constructor() {
this.accentColor = '124,106,255';
this.baseColor = '255,255,255';
this.color = this.baseColor;
this.reset();
}
reset() {
this.x = Math.random() * W;
this.y = Math.random() * H;
this.z = Math.random() * 3 + 0.5;
this.vx = (Math.random() - 0.5) * 0.3;
this.vy = (Math.random() - 0.5) * 0.3;
this.r = Math.random() * 2 + 0.8;
this.alpha = Math.random() * 0.55 + 0.15;
this.color = Math.random() > 0.7 ? this.accentColor : this.baseColor;
}
update() {
this.x += this.vx;
this.y += this.vy;
const dx = this.x - mouseX, dy = this.y - mouseY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150) {
const f = (150 - dist) / 150;
this.vx += (dx / dist) * f * 0.3;
this.vy += (dy / dist) * f * 0.3;
}
this.vx *= 0.99;
this.vy *= 0.99;
if (this.x < -10 || this.x > W + 10 || this.y < -10 || this.y > H + 10) this.reset();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.r * this.z, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${this.color},${this.alpha})`;
ctx.fill();
}
}
for (let i = 0; i < 280; i++) particles.push(new Particle());
updateParticleColors();
function drawLines() {
const s = getComputedStyle(document.body);
const lc = s.getPropertyValue('--canvas-line').trim() || '124,106,255';
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 140) {
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = `rgba(${lc},${0.08 * (1 - dist / 140)})`;
ctx.lineWidth = 0.5;
ctx.stroke();
}
}
}
}
(function animLoop() {
ctx.clearRect(0, 0, W, H);
particles.forEach(p => { p.update(); p.draw(); });
drawLines();
requestAnimationFrame(animLoop);
})();
// ── CURSOR GLOW ──
const glowEl = document.getElementById('cursorGlow');
document.addEventListener('mousemove', e => {
mouseX = e.clientX; mouseY = e.clientY;
glowEl.style.left = e.clientX + 'px';
glowEl.style.top = e.clientY + 'px';
});
// ── NAVBAR ──
const navbar = document.getElementById('navbar');
window.addEventListener('scroll', () => navbar.classList.toggle('scrolled', window.scrollY > 20));
// ── MOBILE NAV ──
const hamburger = document.getElementById('hamburger');
const navLinks = document.getElementById('navLinks');
hamburger.addEventListener('click', () => {
hamburger.classList.toggle('active');
navLinks.classList.toggle('active');
});
document.querySelectorAll('.nav-links a').forEach(l => {
l.addEventListener('click', () => {
hamburger.classList.remove('active');
navLinks.classList.remove('active');
});
});
// ── TYPING ──
const typedEl = document.getElementById('typed');
const words = ['web applications', 'AI systems', 'scalable backends', 'RAG pipelines', 'intelligent agents'];
let wi = 0, ci = 0, deleting = false;
function type() {
const w = words[wi];
if (deleting) { typedEl.textContent = w.substring(0, ci - 1); ci--; }
else { typedEl.textContent = w.substring(0, ci + 1); ci++; }
let wait = deleting ? 40 : 80;
if (!deleting && ci === w.length) { wait = 2000; deleting = true; }
else if (deleting && ci === 0) { deleting = false; wi = (wi + 1) % words.length; wait = 300; }
setTimeout(type, wait);
}
type();
// ── COUNTERS ──
let countersDone = false;
function animateCounters() {
if (countersDone) return;
countersDone = true;
document.querySelectorAll('.counter').forEach(el => {
const target = parseFloat(el.dataset.target);
const dec = el.dataset.decimal === 'true';
const start = performance.now();
function tick(now) {
const p = Math.min((now - start) / 1500, 1);
const ease = 1 - Math.pow(1 - p, 3);
el.textContent = dec ? (ease * target).toFixed(2) : Math.floor(ease * target).toLocaleString();
if (p < 1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
}
setTimeout(animateCounters, 600);
// ── 3D TILT ──
document.querySelectorAll('[data-tilt]').forEach(card => {
card.addEventListener('mousemove', e => {
const r = card.getBoundingClientRect();
const x = e.clientX - r.left, y = e.clientY - r.top;
const rx = ((y - r.height / 2) / (r.height / 2)) * -8;
const ry = ((x - r.width / 2) / (r.width / 2)) * 8;
card.style.transform = `perspective(600px) rotateX(${rx}deg) rotateY(${ry}deg) translateZ(10px) scale(1.02)`;
const g = card.querySelector('.proj-glow');
if (g) { g.style.setProperty('--mx', (x / r.width * 100) + '%'); g.style.setProperty('--my', (y / r.height * 100) + '%'); }
});
card.addEventListener('mouseleave', () => {
card.style.transform = 'perspective(600px) rotateX(0) rotateY(0) translateZ(0) scale(1)';
});
});
// ── REVEAL ON SCROLL ──
const revealEls = document.querySelectorAll('.reveal-3d');
revealEls.forEach(el => el.classList.add('hidden-init'));
const revealObs = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.remove('hidden-init');
revealObs.unobserve(entry.target);
}
});
}, { threshold: 0.05, rootMargin: '50px 0px 0px 0px' });
revealEls.forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.top < window.innerHeight + 50) el.classList.remove('hidden-init');
else revealObs.observe(el);
});
// ── PARALLAX ──
document.addEventListener('mousemove', e => {
const mx = (e.clientX / window.innerWidth - 0.5) * 2;
const my = (e.clientY / window.innerHeight - 0.5) * 2;
document.querySelectorAll('.floating-shape').forEach((s, i) => {
const d = (i + 1) * 8;
s.style.marginLeft = mx * d + 'px';
s.style.marginTop = my * d + 'px';
});
});
// ── SMOOTH SCROLL ──
document.querySelectorAll('a[href^="#"]').forEach(a => {
a.addEventListener('click', e => {
e.preventDefault();
const t = document.querySelector(a.getAttribute('href'));
if (t) t.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
});
// ── SCROLL PROGRESS BAR ──
const scrollProgress = document.getElementById('scrollProgress');
window.addEventListener('scroll', () => {
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight;
const progress = scrollHeight > 0 ? (scrollTop / scrollHeight) * 100 : 0;
scrollProgress.style.width = progress + '%';
});
// ── ACTIVE NAV HIGHLIGHT ──
const sections = document.querySelectorAll('section[id]');
const navLinksAll = document.querySelectorAll('.nav-links a');
const navObs = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute('id');
navLinksAll.forEach(link => {
link.classList.toggle('active', link.getAttribute('href') === '#' + id);
});
}
});
}, { threshold: 0.3, rootMargin: '-64px 0px -50% 0px' });
sections.forEach(s => navObs.observe(s));