PageSourceSearch

https://uoa-iai.github.io/main.js?v=2

js uoa-iai.github.io collected 2026-10-03 10:07:22 UTC 8,784 bytes, 232 lines download raw bytes

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.