Portfolio / main.js
ministerchief's picture
Upload 5 files
d14121a verified
Raw
History Blame Contribute Delete
8.46 kB
/* =========================================================
Kartik Kumar Portfolio v2 β€” script.js
========================================================= */
/* ── CURSOR ─────────────────────────────────────────── */
const cursor = document.getElementById('cursor');
const ring = document.getElementById('cursorRing');
let mx=0,my=0,rx=0,ry=0;
document.addEventListener('mousemove',e=>{
mx=e.clientX; my=e.clientY;
cursor.style.left=mx+'px'; cursor.style.top=my+'px';
});
(function loopRing(){
rx+=(mx-rx)*.13; ry+=(my-ry)*.13;
ring.style.left=rx+'px'; ring.style.top=ry+'px';
requestAnimationFrame(loopRing);
})();
document.querySelectorAll('a,button,.skill-card,.project-card,.exp-card-inner,.contact-item').forEach(el=>{
el.addEventListener('mouseenter',()=>{ ring.style.width='54px'; ring.style.height='54px'; ring.style.borderColor='var(--accent2)'; });
el.addEventListener('mouseleave',()=>{ ring.style.width='34px'; ring.style.height='34px'; ring.style.borderColor='var(--accent)'; });
});
/* ── NAVBAR SCROLL ──────────────────────────────────── */
const navbar=document.getElementById('navbar');
window.addEventListener('scroll',()=>{ navbar.classList.toggle('scrolled',window.scrollY>60); });
/* ── HAMBURGER ──────────────────────────────────────── */
const ham=document.getElementById('hamburger');
const navLinks=document.getElementById('navLinks');
ham.addEventListener('click',()=>{
const open=navLinks.classList.toggle('open');
const s=ham.querySelectorAll('span');
s[0].style.transform=open?'rotate(45deg) translate(5px,5px)':'';
s[1].style.opacity=open?'0':'';
s[2].style.transform=open?'rotate(-45deg) translate(5px,-5px)':'';
});
navLinks.querySelectorAll('a').forEach(a=>a.addEventListener('click',()=>{
navLinks.classList.remove('open');
ham.querySelectorAll('span').forEach(s=>{s.style.transform='';s.style.opacity='';});
}));
/* ── TYPING ANIMATION ───────────────────────────────── */
const phrases=['AI/ML Developer','Full Stack Developer','App Developer','Prompt Engineer','Computer Vision Engineer'];
let pi=0,ci=0,del=false;
const typingEl=document.getElementById('typingText');
function type(){
const cur=phrases[pi];
typingEl.textContent=del?cur.slice(0,--ci):cur.slice(0,++ci);
let d=del?55:105;
if(!del&&ci===cur.length){d=2000;del=true;}
else if(del&&ci===0){del=false;pi=(pi+1)%phrases.length;d=350;}
setTimeout(type,d);
}
type();
/* ── THREE.JS HERO ──────────────────────────────────── */
(function initThree(){
const canvas=document.getElementById('heroCanvas');
if(!canvas||!window.THREE)return;
const renderer=new THREE.WebGLRenderer({canvas,alpha:true,antialias:true});
renderer.setPixelRatio(Math.min(devicePixelRatio,2));
renderer.setSize(innerWidth,innerHeight);
const scene=new THREE.Scene();
const camera=new THREE.PerspectiveCamera(60,innerWidth/innerHeight,.1,1000);
camera.position.z=6;
/* Stars */
const starGeo=new THREE.BufferGeometry();
const starPos=new Float32Array(5000*3);
for(let i=0;i<starPos.length;i++) starPos[i]=(Math.random()-.5)*140;
starGeo.setAttribute('position',new THREE.BufferAttribute(starPos,3));
scene.add(new THREE.Points(starGeo,new THREE.PointsMaterial({color:0xffffff,size:.15,transparent:true,opacity:.7})));
/* Torus Knot */
const knot=new THREE.Mesh(
new THREE.TorusKnotGeometry(1,.3,200,24),
new THREE.MeshStandardMaterial({color:0x38bdf8,emissive:0x818cf8,emissiveIntensity:.5,metalness:.85,roughness:.1})
);
knot.position.set(4,.2,-1);
scene.add(knot);
/* Particle ring around knot */
const rCount=700,rPos=new Float32Array(rCount*3);
for(let i=0;i<rCount;i++){
const a=(i/rCount)*Math.PI*2,r=2.5+(Math.random()-.5)*.6;
rPos[i*3]=Math.cos(a)*r; rPos[i*3+1]=(Math.random()-.5)*.3; rPos[i*3+2]=Math.sin(a)*r;
}
const rGeo=new THREE.BufferGeometry();
rGeo.setAttribute('position',new THREE.BufferAttribute(rPos,3));
const ringMesh=new THREE.Points(rGeo,new THREE.PointsMaterial({color:0x38bdf8,size:.055,transparent:true,opacity:.6}));
ringMesh.position.copy(knot.position); scene.add(ringMesh);
/* Floaters */
const floaters=[],cols=[0x38bdf8,0x818cf8,0xf472b6,0xfbbf24];
for(let i=0;i<14;i++){
const m=new THREE.Mesh(
new THREE.IcosahedronGeometry(Math.random()*.22+.07,0),
new THREE.MeshStandardMaterial({color:cols[i%4],emissive:cols[i%4],emissiveIntensity:.6,wireframe:Math.random()>.5})
);
m.position.set((Math.random()-.5)*12,(Math.random()-.5)*7,(Math.random()-.5)*5-1);
m.userData={rx:(Math.random()-.5)*.022,ry:(Math.random()-.5)*.022,baseY:m.position.y};
scene.add(m); floaters.push(m);
}
/* Lights */
scene.add(new THREE.AmbientLight(0xffffff,.4));
const pl1=new THREE.PointLight(0x38bdf8,2.5,22); pl1.position.set(3,3,3); scene.add(pl1);
const pl2=new THREE.PointLight(0x818cf8,2.5,22); pl2.position.set(-3,-2,2); scene.add(pl2);
const pl3=new THREE.PointLight(0xf472b6,1.5,15); pl3.position.set(0,4,-2); scene.add(pl3);
let mmx=0,mmy=0;
window.addEventListener('mousemove',e=>{ mmx=(e.clientX/innerWidth-.5)*2; mmy=(e.clientY/innerHeight-.5)*2; });
window.addEventListener('resize',()=>{
camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix();
renderer.setSize(innerWidth,innerHeight);
});
let t=0;
(function animate(){
requestAnimationFrame(animate); t+=.008;
knot.rotation.x+=.007; knot.rotation.y+=.011; knot.rotation.z+=.004;
ringMesh.rotation.y+=.005; ringMesh.rotation.x=.4;
floaters.forEach(f=>{ f.rotation.x+=f.userData.rx; f.rotation.y+=f.userData.ry; f.position.y=f.userData.baseY+Math.sin(t+f.userData.baseY)*.35; });
camera.position.x+=(mmx*.6-camera.position.x)*.04;
camera.position.y+=(-mmy*.35-camera.position.y)*.04;
camera.lookAt(scene.position);
renderer.render(scene,camera);
})();
})();
/* ── SCROLL AOS ──────────────────────────────────────── */
const obs=new IntersectionObserver(entries=>{
entries.forEach(e=>{ if(e.isIntersecting){ e.target.classList.add('in-view'); obs.unobserve(e.target); } });
},{threshold:.1});
document.querySelectorAll('[data-aos]').forEach(el=>obs.observe(el));
/* skill bars */
const skillObs=new IntersectionObserver(entries=>{
if(entries[0].isIntersecting){
document.querySelectorAll('.skill-fill').forEach(b=>{ b.style.width=b.dataset.width+'%'; });
skillObs.unobserve(entries[0].target);
}
},{threshold:.2});
const sk=document.getElementById('skills');
if(sk) skillObs.observe(sk);
/* ── NAV ACTIVE HIGHLIGHT ────────────────────────────── */
const sections=document.querySelectorAll('section[id]');
const anchors=document.querySelectorAll('.nav-links a');
function hlNav(){
let cur='';
sections.forEach(s=>{ if(window.scrollY>=s.offsetTop-130) cur=s.id; });
anchors.forEach(a=>{ a.classList.toggle('active',a.getAttribute('href')==='#'+cur); });
}
window.addEventListener('scroll',hlNav); hlNav();
/* ── HERO PARALLAX ───────────────────────────────────── */
window.addEventListener('scroll',()=>{
const y=window.scrollY;
const hc=document.querySelector('.hero-content');
if(hc) hc.style.transform=`translateY(${y*.22}px)`;
});
/* ── CONTACT FORM ────────────────────────────────────── */
const form=document.getElementById('contactForm');
form&&form.addEventListener('submit',async e=>{
e.preventDefault();
const btn=form.querySelector('button[type=submit]');
const orig=btn.innerHTML; btn.innerHTML='Sending…'; btn.disabled=true;
await new Promise(r=>setTimeout(r,1300));
btn.innerHTML='βœ“ Sent!';
document.getElementById('formSuccess').style.display='block';
form.reset();
setTimeout(()=>{ btn.innerHTML=orig; btn.disabled=false; document.getElementById('formSuccess').style.display='none'; },4000);
});