${item.summary}
/* ══════════════════════════════════════════ RECOVER LAB — main.js Advanced interactions & animations ══════════════════════════════════════════ */ 'use strict'; /* ── LOADER ── */ (function initLoader() { const loader = document.getElementById('loader'); const fill = document.getElementById('loaderFill'); if (!loader || !fill) return; let progress = 0; const tick = setInterval(() => { progress += Math.random() * 18 + 5; if (progress >= 100) { progress = 100; clearInterval(tick); setTimeout(() => loader.classList.add('hidden'), 300); } fill.style.width = progress + '%'; }, 60); })(); /* ── HERO CANVAS — particle network ── */ (function initCanvas() { const canvas = document.getElementById('heroCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); let W, H, particles = [], mouse = { x: -9999, y: -9999 }; const PARTICLE_COUNT = 80; const CONNECT_DIST = 120; const MOUSE_RADIUS = 160; function getAccent() { return getComputedStyle(document.documentElement) .getPropertyValue('--accent').trim() || '#00c2aa'; } class Particle { constructor() { this.reset(); } reset() { this.x = Math.random() * W; this.y = Math.random() * H; this.vx = (Math.random() - 0.5) * 0.4; this.vy = (Math.random() - 0.5) * 0.4; this.r = Math.random() * 2 + 1; this.base = { x: this.x, y: this.y }; } update() { this.x += this.vx; this.y += this.vy; if (this.x < 0 || this.x > W) this.vx *= -1; if (this.y < 0 || this.y > H) this.vy *= -1; // mouse repulsion const dx = this.x - mouse.x; const dy = this.y - mouse.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < MOUSE_RADIUS) { const force = (MOUSE_RADIUS - dist) / MOUSE_RADIUS; this.x += dx / dist * force * 2; this.y += dy / dist * force * 2; } } draw(accent) { ctx.beginPath(); ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2); ctx.fillStyle = accent; ctx.globalAlpha = 0.5; ctx.fill(); ctx.globalAlpha = 1; } } function resize() { W = canvas.width = canvas.offsetWidth; H = canvas.height = canvas.offsetHeight; particles = Array.from({ length: PARTICLE_COUNT }, () => new Particle()); } function draw() { ctx.clearRect(0, 0, W, H); const accent = getAccent(); for (let i = 0; i < particles.length; i++) { particles[i].update(); particles[i].draw(accent); 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 d = Math.sqrt(dx * dx + dy * dy); if (d < CONNECT_DIST) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = accent; ctx.globalAlpha = (1 - d / CONNECT_DIST) * 0.2; ctx.lineWidth = 0.8; ctx.stroke(); ctx.globalAlpha = 1; } } } requestAnimationFrame(draw); } window.addEventListener('resize', resize); canvas.addEventListener('mousemove', e => { const rect = canvas.getBoundingClientRect(); mouse.x = e.clientX - rect.left; mouse.y = e.clientY - rect.top; }); canvas.addEventListener('mouseleave', () => { mouse.x = -9999; mouse.y = -9999; }); resize(); draw(); })(); /* ── NAV SCROLL ── */ (function initNav() { const nav = document.getElementById('nav'); window.addEventListener('scroll', () => { nav.classList.toggle('scrolled', window.scrollY > 40); }, { passive: true }); // Hamburger const hamburger = document.getElementById('hamburger'); const navLinks = document.getElementById('navLinks'); if (!hamburger || !navLinks) return; hamburger.addEventListener('click', () => { navLinks.classList.toggle('open'); const spans = hamburger.querySelectorAll('span'); const isOpen = navLinks.classList.contains('open'); spans[0].style.transform = isOpen ? 'translateY(6.5px) rotate(45deg)' : ''; spans[1].style.opacity = isOpen ? '0' : ''; spans[2].style.transform = isOpen ? 'translateY(-6.5px) rotate(-45deg)' : ''; }); navLinks.querySelectorAll('a').forEach(a => a.addEventListener('click', () => { navLinks.classList.remove('open'); hamburger.querySelectorAll('span').forEach(s => { s.style.transform = ''; s.style.opacity = ''; }); }) ); })(); /* ── THEME PANEL ── */ (function initTheme() { const html = document.documentElement; if (!html.dataset.mode) html.dataset.mode = 'dark'; })(); /* ── SCROLL REVEAL ── */ (function initReveal() { const els = document.querySelectorAll('.reveal-up, .reveal-left, .reveal-right'); const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target); } }); }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); els.forEach(el => { obs.observe(el); // Check if element is already in view const rect = el.getBoundingClientRect(); if (rect.top < window.innerHeight && rect.bottom > 0) { el.classList.add('visible'); obs.unobserve(el); } }); })(); /* ── COUNTER ANIMATION ── */ (function initCounters() { const stats = document.querySelectorAll('.hero-stat'); if (!stats.length) return; const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (!e.isIntersecting) return; const el = e.target; const target = parseInt(el.dataset.count); const suffix = el.dataset.suffix || ''; const numEl = el.querySelector('.stat-n'); const prefix = numEl.textContent.startsWith('$') ? '$' : ''; let current = 0; const step = target / 40; const timer = setInterval(() => { current += step; if (current >= target) { current = target; clearInterval(timer); } numEl.textContent = prefix + Math.floor(current) + suffix; }, 30); obs.unobserve(el); }); }, { threshold: 0.5 }); stats.forEach(s => obs.observe(s)); })(); /* ── ACTIVE NAV LINK ON SCROLL ── */ (function initActiveNav() { const navLinks = document.querySelectorAll('.nav-link'); if (!navLinks.length) return; const page = window.location.pathname.split('/').pop() || 'index.html'; const pageMap = { 'index.html': ['#about', '#contact'], 'research.html': ['research.html'], 'team.html': ['team.html'], 'publications.html': ['publications.html'], 'news.html': ['news.html'] }; function setActiveByHref(href) { navLinks.forEach(l => l.classList.toggle('active', l.getAttribute('href') === href)); } if (page !== 'index.html') { const matches = pageMap[page]; if (matches && matches.length) setActiveByHref(matches[0]); return; } const sections = document.querySelectorAll('section[id]'); setActiveByHref('#about'); const obs = new IntersectionObserver(entries => { entries.forEach(e => { if (e.isIntersecting) { const targetHref = `#${e.target.id}`; if (targetHref === '#about' || targetHref === '#contact') { setActiveByHref(targetHref); } } }); }, { threshold: 0.4 }); sections.forEach(s => obs.observe(s)); })(); /* ── CONTACT FORM ── */ (function initForm() { const form = document.getElementById('contactForm'); if (!form) return; form.addEventListener('submit', e => { e.preventDefault(); const formData = new FormData(form); const firstName = (formData.get('firstName') || '').toString().trim(); const lastName = (formData.get('lastName') || '').toString().trim(); const email = (formData.get('email') || '').toString().trim(); const inquiryType = (formData.get('inquiryType') || 'General Question').toString().trim(); const message = (formData.get('message') || '').toString().trim(); const sender = [firstName, lastName].filter(Boolean).join(' ') || 'Website visitor'; const subject = encodeURIComponent(`RECOVER Lab Inquiry: ${inquiryType}`); const body = encodeURIComponent( `Name: ${sender}\nEmail: ${email || 'Not provided'}\nInquiry Type: ${inquiryType}\n\nMessage:\n${message || 'No message provided.'}` ); const btn = form.querySelector('button[type="submit"]'); const orig = btn.textContent; btn.textContent = 'Opening Email Draft...'; btn.style.background = 'var(--accent-2)'; btn.disabled = true; window.location.href = `mailto:arunrajtpr19@gmail.com?subject=${subject}&body=${body}`; setTimeout(() => { btn.textContent = orig; btn.style.background = ''; btn.disabled = false; form.reset(); }, 3000); }); })(); /* ── PARALLAX HERO CONTENT ── */ (function initParallax() { const hero = document.getElementById('hero'); const content = hero ? hero.querySelector('.hero-content') : null; if (!content) return; window.addEventListener('scroll', () => { const scrollY = window.scrollY; if (scrollY < window.innerHeight) { content.style.transform = `translateY(${scrollY * 0.2}px)`; content.style.opacity = 1 - scrollY / (window.innerHeight * 0.75); } }, { passive: true }); })(); /* ── MAGNETIC BUTTONS ── */ (function initMagnetic() { document.querySelectorAll('.btn-glow, .btn-ghost').forEach(btn => { btn.addEventListener('mousemove', e => { const rect = btn.getBoundingClientRect(); const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2; const dx = (e.clientX - cx) * 0.2; const dy = (e.clientY - cy) * 0.2; btn.style.transform = `translate(${dx}px, ${dy}px) translateY(-2px)`; }); btn.addEventListener('mouseleave', () => { btn.style.transform = ''; }); }); })(); /* ── DATA RENDERING FUNCTIONS ── */ // Fetch JSON data from /data/ directory async function fetchJSON(filename) { try { const response = await fetch(`./data/${filename}`); if (!response.ok) throw new Error(`Failed to fetch ${filename}`); return await response.json(); } catch (error) { console.error(`Error fetching ${filename}:`, error); return null; } } // Render team members from JSON async function renderTeamData() { const piGrid = document.querySelector('#teamPiGrid'); const researchersGrid = document.querySelector('#teamResearchersGrid'); const studentsGrid = document.querySelector('#teamStudentsGrid'); const researchersSection = document.querySelector('#teamResearchersSection'); if (!piGrid || !researchersGrid || !studentsGrid) return; const data = await fetchJSON('team.json'); if (!data) return; const piMembers = []; const studentMembers = []; const researcherMembers = []; data.forEach(member => { const role = (member.role || '').toLowerCase(); if (role.includes('principal investigator')) piMembers.push(member); else if (role.includes('student') || role.includes('candidate')) studentMembers.push(member); else researcherMembers.push(member); }); renderTeamGroup(piGrid, piMembers); renderTeamGroup(researchersGrid, researcherMembers); renderTeamGroup(studentsGrid, studentMembers); if (researchersSection) { researchersSection.style.display = researcherMembers.length ? '' : 'none'; } // Re-initialize scroll reveal for new elements initRevealForNewElements(); } function renderTeamGroup(grid, members) { grid.innerHTML = ''; if (!members.length) { grid.innerHTML = '
${project.desc}
${item.summary}
${news.body}
${linkHTML}