1/* ============================================================ 2 Industrial AI Group â Main JavaScript 3 ============================================================ */ 4 5// ââ Navbar scroll effect ââââââââââââââââââââââââââââââââââ 6const navbar = document.getElementById('navbar'); 7window.addEventListener('scroll', () => { 8 navbar.classList.toggle('scrolled', window.scrollY > 40); 9}); 10 11// ââ Mobile hamburger ââââââââââââââââââââââââââââââââââââââ 12const hamburger = document.getElementById('hamburger'); 13const mobileMenu = document.getElementById('mobileMenu'); 14hamburger.addEventListener('click', () => { 15 mobileMenu.classList.toggle('open'); 16}); 17mobileMenu.querySelectorAll('a').forEach(a => { 18 a.addEventListener('click', () => mobileMenu.classList.remove('open')); 19}); 20 21// ââ Grid canvas background ââââââââââââââââââââââââââââââââ 22(function initGrid() { 23 const canvas = document.getElementById('gridCanvas'); 24 if (!canvas) return; 25 const ctx = canvas.getContext('2d'); 26 let W, H, cols, rows; 27 function resize() { 28 W = canvas.width = canvas.offsetWidth; 29 H = canvas.height = canvas.offsetHeight; 30 cols = Math.ceil(W / 48); 31 rows = Math.ceil(H / 48); 32 draw(); 33 } 34 function draw() { 35 const isLight = document.documentElement.getAttribute('data-theme') === 'light'; 36 const lineColor = isLight ? 'rgba(29,110,197,0.1)' : 'rgba(99,179,237,0.18)'; 37 const dotColor = isLight ? 'rgba(29,110,197,0.2)' : 'rgba(99,179,237,0.35)'; 38 ctx.clearRect(0, 0, W, H); 39 ctx.strokeStyle = lineColor; 40 ctx.lineWidth = 0.5; 41 const sx = W / cols, sy = H / rows; 42 for (let c = 0; c <= cols; c++) { 43 ctx.beginPath(); ctx.moveTo(c * sx, 0); ctx.lineTo(c * sx, H); ctx.stroke(); 44 } 45 for (let r = 0; r <= rows; r++) { 46 ctx.beginPath(); ctx.moveTo(0, r * sy); ctx.lineTo(W, r * sy); ctx.stroke(); 47 } 48 ctx.fillStyle = dotColor; 49 for (let c = 0; c <= cols; c++) { 50 for (let r = 0; r <= rows; r++) { 51 ctx.beginPath(); 52 ctx.arc(c * sx, r * sy, 1.2, 0, Math.PI * 2); 53 ctx.fill(); 54 } 55 } 56 } 57 canvas._redraw = draw; 58 window.addEventListener('resize', resize); 59 resize(); 60})(); 61 62// ââ Tag label map âââââââââââââââââââââââââââââââââââââââââ 63const TAG_LABELS = { 64 automation: 'Factory Automation', 65 hrc: 'Human-Robot', 66 embodied: 'Embodied AI', 67}; 68 69// ââ Render publications âââââââââââââââââââââââââââââââââââ 70function renderPublications(area) { 71 const all = getPublications(); 72 const activeArea = area || 'automation'; 73 const list = [...all] 74 .filter(p => p.tag === activeArea) 75 .sort((a, b) => b.year - a.year) 76 .slice(0, 8); 77 const container = document.getElementById('pubList'); 78 if (!container) return; 79 80 container.innerHTML = list.map(p => ` 81 <div class="pub-item" data-tag="${p.tag}"> 82 <div class="pub-year">${p.year}</div> 83 <div class="pub-content"> 84 <span class="pub-tag ${p.tag}">${TAG_LABELS[p.tag] || p.tag}</span> 85 <h4>${p.title}</h4> 86 <p class="pub-authors">${p.authors}</p> 87 <p class="pub-venue">${p.venue}</p> 88 <div class="pub-links"> 89 ${p.links.map(l => `<a href="${l.url}">${l.label}</a>`).join('')} 90 </div> 91 </div> 92 </div> 93 `).join(''); 94 95 addReveal(); 96} 97 98// ââ Publication area tabs âââââââââââââââââââââââââââââââââ 99function initPubTabs() { 100 const tabs = document.querySelectorAll('.pub-area-tab');
101 tabs.forEach(tab => { 102 tab.addEventListener('click', () => { 103 tabs.forEach(t => t.classList.remove('active')); 104 tab.classList.add('active'); 105 renderPublications(tab.dataset.area); 106 }); 107 }); 108} 109 110// ââ Render news âââââââââââââââââââââââââââââââââââââââââââ 111function renderNews() { 112 const list = getNews().slice(0, 5); 113 const container = document.getElementById('newsGrid'); 114 if (!container) return; 115 116 container.innerHTML = list.map((n, i) => ` 117 <div class="news-card ${i === 0 ? 'featured-news' : ''}"> 118 ${n.image ? `<img src="${n.image}" alt="${n.title}" class="news-card-img" loading="lazy" />` : ''} 119 <div class="news-card-body"> 120 <div class="news-card-meta"> 121 <span class="news-date">${n.date}</span> 122 <span class="news-badge">${n.badge}</span> 123 </div> 124 <h4>${n.title}</h4> 125 <p>${n.body}</p> 126 <a href="news/${n.id}.html">${n.linkText} â</a> 127 </div> 128 </div> 129 `).join(''); 130 131 addReveal(); 132} 133 134// ââ Publication filters âââââââââââââââââââââââââââââââââââ 135function initPubFilters() { 136 const filters = document.querySelectorAll('.pub-filter'); 137 const pubItems = document.querySelectorAll('.pub-item'); 138 filters.forEach(btn => { 139 btn.addEventListener('click', () => { 140 filters.forEach(b => b.classList.remove('active')); 141 btn.classList.add('active');
142 const f = btn.dataset.filter; 143 pubItems.forEach(item => { 144 item.classList.toggle('hidden', f !== 'all' && item.dataset.tag !== f); 145 }); 146 }); 147 }); 148} 149 150// ââ Scroll reveal âââââââââââââââââââââââââââââââââââââââââ 151function addReveal() { 152 const selectors = [ 153 '.research-card', '.team-card', '.pub-item', 154 '.news-card', '.team-pi', '.contact-layout', '.about-text', 155 '.initiative-card', '.video-featured', '.strip-item', '.facility-card', 156 ]; 157 selectors.forEach(sel => { 158 document.querySelectorAll(sel).forEach((el, i) => { 159 if (el.classList.contains('reveal')) return; // already wired 160 el.classList.add('reveal'); 161 el.style.transitionDelay = `${i * 60}ms`; 162 }); 163 }); 164 165 const observer = new IntersectionObserver((entries) => { 166 entries.forEach(e => { 167 if (e.isIntersecting) { 168 e.target.classList.add('visible'); 169 observer.unobserve(e.target); 170 } 171 }); 172 }, { threshold: 0.08 }); 173 174 document.querySelectorAll('.reveal:not(.visible)').forEach(el => observer.observe(el)); 175} 176 177// ââ Contact form ââââââââââââââââââââââââââââââââââââââââââ 178function handleForm(e) { 179 e.preventDefault(); 180 const note = document.getElementById('formNote'); 181 note.textContent = 'Message sent! We will get back to you shortly.'; 182 e.target.reset(); 183 setTimeout(() => { note.textContent = ''; }, 5000); 184} 185 186// ââ Video showcase ââââââââââââââââââââââââââââââââââââââââ 187function initVideoGallery() { 188 const frame = document.getElementById('featuredFrame'); 189 const featTag = document.getElementById('featuredTag'); 190 const featTitle = document.getElementById('featuredTitle'); 191 const featDesc = document.getElementById('featuredDesc'); 192 const strip = document.getElementById('videoStrip'); 193 const prevBtn = document.getElementById('stripPrev'); 194 const nextBtn = document.getElementById('stripNext'); 195 if (!frame || !strip) return; 196 197 const items = strip.querySelectorAll('.strip-item'); 198 199 function selectItem(item) { 200 items.forEach(i => i.classList.remove('active')); 201 item.classList.add('active'); 202 frame.src = item.dataset.src; 203 featTag.textContent = item.dataset.tag; 204 featTitle.textContent = item.dataset.title; 205 featDesc.textContent = item.dataset.desc; 206 } 207 208 items.forEach(item => { 209 item.addEventListener('click', () => selectItem(item)); 210 }); 211 212 function updateArrows() { 213 prevBtn.disabled = strip.scrollLeft <= 0; 214 nextBtn.disabled = strip.scrollLeft + strip.clientWidth >= strip.scrollWidth - 1; 215 } 216 217 prevBtn.addEventListener('click', () => { 218 strip.scrollBy({ left: -strip.clientWidth * 0.6, behavior: 'smooth' }); 219 }); 220 nextBtn.addEventListener('click', () => { 221 strip.scrollBy({ left: strip.clientWidth * 0.6, behavior: 'smooth' }); 222 }); 223 strip.addEventListener('scroll', updateArrows, { passive: true }); 224 updateArrows(); 225} 226 227// ââ Init ââââââââââââââââââââââââââââââââââââââââââââââââââ 228renderPublications('automation'); 229initPubTabs(); 230renderNews(); 231initVideoGallery(); 232addReveal();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.