Download script.js from nehal2006/portfolio: direct link, hf CLI and curl.
- Browser
- Download file 8.71 kB
-
https://huggingface.co/spaces/nehal2006/portfolio/resolve/main/script.js
- Command line
-
hf download hf://spaces/nehal2006/portfolio/script.js
-
curl -L -o script.js https://huggingface.co/spaces/nehal2006/portfolio/resolve/main/script.js
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)); | |