PageSourceSearch

https://naisslab.github.io/

html naisslab.github.io collected 2026-10-03 09:39:22 UTC 32,121 bytes, 570 lines download raw bytes

1<!DOCTYPE html>
2<html lang="en" class="scroll-smooth">
3
4<head>
5    <meta charset="UTF-8">
6    <meta name="viewport" content="width=device-width, initial-scale=1.0">
7    <title>NAISS Lab | Networked AI Systems and Security Lab</title>
8    <link rel="stylesheet" href="assets/site.css?v=786269ed3f9d">
9    <link rel="preconnect" href="https://fonts.googleapis.com">
10    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11    <link
12        href="https://fonts.googleapis.com/css2?family=Noto+Serif:wght@500;600;700&amp;family=Noto+Sans:wght@400;500;600;700&amp;display=swap"
13        rel="stylesheet">
14</head>
15
16<body class="antialiased">
17
18    <header id="header" class="site-header fixed top-0 left-0 right-0 z-50 shadow-sm">
19        <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
20            <div class="flex items-center justify-between h-16 sm:h-[4.35rem]">
21                <div class="flex-shrink-0">
22                    <a href="#home" class="lab-brand text-3xl leading-none font-semibold">NAISS Lab</a>
23                </div>
24                <div class="hidden md:block">
25                    <nav class="ml-8 flex items-baseline gap-1 lg:gap-3">
26                        <a href="#about" class="nav-link px-3 py-2 text-xs sm:text-sm uppercase">About</a>
27                        <a href="#research" class="nav-link px-3 py-2 text-xs sm:text-sm uppercase">Research</a>
28                        <a href="#people" class="nav-link px-3 py-2 text-xs sm:text-sm uppercase">People</a>
29                        <a href="#publications" class="nav-link px-3 py-2 text-xs sm:text-sm uppercase">Publications</a>
30                        <a href="#news" class="nav-link px-3 py-2 text-xs sm:text-sm uppercase">News</a>
31                    </nav>
32                </div>
33                <div class="-mr-1 flex md:hidden">
34                    <button id="mobile-menu-button" type="button"
35                        class="inline-flex items-center justify-center p-2 rounded-md border border-slate-200 bg-white text-slate-600 hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-teal-900/20"
36                        aria-controls="mobile-menu" aria-expanded="false">
37                        <span class="sr-only">Open main menu</span>
38                        <svg class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
39                            stroke="currentColor" aria-hidden="true">
40                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
41                                d="M4 6h16M4 12h16M4 18h16" />
42                        </svg>
43                        <svg class="hidden h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
44                            stroke="currentColor" aria-hidden="true">
45                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
46                                d="M6 18L18 6M6 6l12 12" />
47                        </svg>
48                    </button>
49                </div>
50            </div>
51        </div>
52        <div class="md:hidden hidden border-t border-slate-200 bg-white/95 shadow-lg" id="mobile-menu">
53            <div class="px-3 py-3 space-y-1">
54                <a href="#about" class="block nav-link px-3 py-2 rounded-md text-sm uppercase">About</a>
55                <a href="#research" class="block nav-link px-3 py-2 rounded-md text-sm uppercase">Research</a>
56                <a href="#people" class="block nav-link px-3 py-2 rounded-md text-sm uppercase">People</a>
57                <a href="#publications" class="block nav-link px-3 py-2 rounded-md text-sm uppercase">Publications</a>
58                <a href="#news" class="block nav-link px-3 py-2 rounded-md text-sm uppercase">News</a>
59            </div>
60        </div>
61    </header>
62
63    <main>
64        <section id="home" class="hero-bg pt-16">
65            <div class="hero-content max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-24 sm:py-28 md:py-36 text-white text-center">
66                <div class="hero-title-wrap mx-auto">
67                    <p class="hero-ghost">ML Systems, Networking, and Security</p>
68                    <h1 class="hero-main-title text-4xl sm:text-5xl lg:text-7xl">
69                        Networked AI Systems and Security Lab
70                        <!-- <span class="block">Security Lab</span> -->
71                    </h1>
72                </div>
73                <p class="hero-lab-name mt-2 sm:mt-3">NAISS Lab</p>
74                <p class="mt-6 max-w-3xl mx-auto text-lg sm:text-xl md:text-2xl text-slate-100/90 leading-relaxed">
75                    Developing the next generation of intelligent systems to ensure our networked world is accessible,
76                    reliable, and secure.
77                </p>
78            </div>
79        </section>
80
81        <section id="about" class="section-shell soft py-16 sm:py-20">
82            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
83                <div class="text-center">
84                    <h2 class="section-title text-4xl font-semibold tracking-tight sm:text-5xl">Our Mission</h2>
85                    <p class="mt-6 max-w-4xl mx-auto text-base sm:text-lg leading-relaxed text-slate-600">
86                        The NAISS (pronounced as "nice") Lab in the Department of Data and Systems Engineering at The University of Hong Kong
87                        operates at the critical intersection of machine learning systems, computer networking, and
88                        security. Our research follows a virtuous cycle: we build foundational ML systems and
89                        techniques, apply them to secure critical network infrastructure, and extend them to improve
90                        interactions and well-beings with the cyber-physical world. Contact us at
91                        <a href="mailto:[email protected]" class="publication-link">[email protected]</a>.
92                    </p>
93                </div>
94            </div>
95        </section>
96
97        <section id="research" class="section-shell py-16 sm:py-20">
98            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
99                <div class="text-center">
100                    <h2 class="section-title text-4xl font-semibold tracking-tight sm:text-5xl">Research Directions</h2>
101                    <p class="mt-6 max-w-4xl mx-auto text-base sm:text-lg text-slate-600">
102                        Explore the topics and publications behind our three research directions.
103                    </p>
104                </div>
105                <div id="research-container" class="mt-12 grid gap-6 md:grid-cols-3">
106                </div>
107            </div>
108        </section>
109
110        <section id="people" class="section-shell soft py-16 sm:py-20">
111            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
112                <div class="text-center">
113                    <h2 class="section-title text-4xl font-semibold tracking-tight sm:text-5xl">Our Team</h2>
114                    <p class="mt-6 max-w-4xl mx-auto text-base sm:text-lg text-slate-600">
115                        Meet the researchers and students driving innovation at the NAISS Lab.
116                    </p>
117                </div>
118                <div class="mt-8 flex justify-center flex-wrap gap-2" id="people-filter-controls">
119                </div>
120                <div id="people-container" class="mt-10 grid gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
121                </div>
122            </div>
123        </section>
124
125        <section id="publications" class="section-shell py-16 sm:py-20">
126            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
127                <div class="text-center">
128                    <h2 class="section-title text-4xl font-semibold tracking-tight sm:text-5xl">Publications</h2>
129                    <p class="mt-6 max-w-4xl mx-auto text-base sm:text-lg text-slate-600">
130                        A selection of our scholarly output.
131                    </p>
132                </div>
133                <div class="mt-8 max-w-4xl mx-auto flex flex-col sm:flex-row gap-3">
134                    <select id="pub-year-filter" aria-label="Publication year or preprints" class="select-control w-full sm:w-1/2 p-2.5 rounded-md">
135                        <option value="all">All Years</option>
136                    </select>
137                    <select id="pub-area-filter" aria-label="Research area" class="select-control w-full sm:w-1/2 p-2.5 rounded-md">
138                        <option value="all">All Research Areas</option>
139                    </select>
140                </div>
141                <div id="publications-container" class="mt-6 grid gap-2.5 max-w-6xl mx-auto">
142                </div>
143            </div>
144        </section>
145
146        <section id="news" class="section-shell soft py-16 sm:py-20">
147            <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
148                <div class="text-center">
149                    <h2 class="section-title text-4xl font-semibold tracking-tight sm:text-5xl">Latest News</h2>
150                </div>
151                <div id="news-container" class="mt-6 max-w-6xl mx-auto grid gap-2.5">
152                </div>
153            </div>
154        </section>
155
156    </main>
157
158    <footer class="bg-slate-900 text-slate-100">
159        <div class="max-w-7xl mx-auto py-8 px-4 sm:px-6 lg:px-8 text-center">
160            <p class="text-sm sm:text-base">&copy; 2026 Networked AI Systems and Security (NAISS) Lab</p>
161            <p class="text-sm text-slate-400 mt-2">The University of Hong Kong</p>
162        </div>
163    </footer>
164
165    
165<script src="data/research.js?v=d6dd5af3a1e9"></script>
165
166    
166<script src="data/people.js?v=f20935bd08d1"></script>
166
167    
167<script src="data/publication.js?v=875cb7ddb51f"></script>
167
168    
168<script src="data/news.js?v=a9906eeffbc7"></script>
168
169
170    
170<script>
171        document.addEventListener('DOMContentLoaded', function () {
172            // Data is now loaded from the external files above.
173            // The large data arrays have been removed from this file.
174            const labMemberNames = new Set([...peopleData, ...alumniData].flatMap(person => [
175                `${person.firstName} ${person.lastName}`,
176                ...(person.aliases || [])
177            ]));
178
179            // Render Functions
180            function escapeHtml(value) {
181                return String(value).replace(/[&<>"']/g, char => ({
182                    '&': '&amp;',
183                    '<': '&lt;',
184                    '>': '&gt;',
185                    '"': '&quot;',
186                    "'": '&#39;'
187                })[char]);
188            }
189
190            function normalizeAuthorName(name) {
191                return name.replace(/\*/g, '').replace(/\s+/g, ' ').trim();
192            }
193
194            function renderPublicationAuthors(authors) {
195                return authors.split(/,\s*/).map(author => {
196                    const authorName = author.trim();
197                    const escapedName = escapeHtml(authorName);
198
199                    if (labMemberNames.has(normalizeAuthorName(authorName))) {
200                        return `<strong class="publication-author-lab-member" title="NAISS Lab member">${escapedName}</strong>`;
201                    }
202
203                    return escapedName;
204                }).join(', ');
205            }
206
207            function renderResearch() {
208                const container = document.getElementById('research-container');
209                if (!container) return;
210                container.innerHTML = researchData.map(item => `
211                    <article class="research-card flex flex-col bg-white/95 p-6 rounded-xl cursor-pointer" data-id="${item.id}">
212                        <div class="text-3xl">${item.icon}</div>
213                        <h3 class="mt-3 text-2xl font-semibold">${item.title}</h3>
214                        <p class="mt-2 text-slate-600 leading-relaxed">${item.description}</p>
215                        <button type="button" data-research-toggle="${item.id}"
216                            class="mt-4 self-start inline-flex items-center gap-2 text-sm font-semibold text-accent rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-800"
217                            aria-expanded="false" aria-controls="subtopics-${item.id}">
218                            Topics &amp; selected work
219                            <svg class="h-4 w-4 transition-transform" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
220                                <path d="m5 7.5 5 5 5-5" />
221                            </svg>
222                        </button>
223                        <div id="subtopics-${item.id}" class="hidden mt-4 pl-4 border-l-2 border-teal-700/30">
224                            <ul class="list-disc list-outside ml-4 text-slate-600 space-y-5 text-sm sm:text-base">
225                                ${item.subtopics.map(sub => `
226                                    <li class="research-subtopic">
227                                        <p class="font-medium leading-relaxed">${escapeHtml(sub.title)}</p>
228                                        <ul class="list-none mt-2 space-y-1.5 text-sm leading-relaxed">
229                                            ${(sub.featuredPublications || []).map(featured => {
230                                                const publication = publicationsData.find(pub => pub.id === featured.id);
231                                                if (!publication) return '';
232                                                const externalUrl = publication.links.website || publication.links.pdf;
233                                                const url = externalUrl || `#publication-${publication.id}`;
234                                                const linkAttributes = externalUrl
235                                                    ? 'target="_blank" rel="noopener noreferrer"'
236                                                    : `data-publication-id="${escapeHtml(publication.id)}"`;
237                                                return `<li><a href="${escapeHtml(url)}" ${linkAttributes}
238                                                    class="publication-link rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-800"
239                                                    title="${escapeHtml(publication.title)}">${escapeHtml(featured.label)}</a></li>`;
240                                            }).join('')}
241                                        </ul>
242                                    </li>
243                                `).join('')}
244                            </ul>
245                        </div>
246                        <a href="#publications" data-research-area="${item.id}"
247                            class="publication-link mt-auto pt-5 self-start inl
247ine-flex items-center gap-2 text-sm rounded-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-teal-800"
248                            aria-label="View publications in ${item.title}">
249                            View related publications (${publicationsData.filter(pub => pub.area.includes(item.id)).length})
250                            <span aria-hidden="true">&rarr;</span>
251                        </a>
252                    </article>
253                `).join('');
254            }
255
256            function renderPersonCards(people) {
257                return people.map(person => {
258                    const personNameHtml = person.website
259                        ? `<a href="${person.website}" target="_blank" rel="noopener noreferrer" class="news-link">${person.firstName} ${person.lastName}</a>`
260                        : `${person.firstName} ${person.lastName}`;
261
262                    return `
263                    <div class="person-card text-center bg-white/90 border border-slate-200 p-5 rounded-xl shadow-sm" data-role="${person.role}">
264                        <img src="${person.photo}" alt="${person.firstName} ${person.lastName}" width="96" height="96" loading="lazy" decoding="async" class="w-24 h-24 rounded-full mx-auto object-cover">
265                        <h3 class="mt-3 font-semibold text-xl">${personNameHtml}</h3>
266                        <p class="text-accent font-semibold text-sm">${person.role}</p>
267                        <p class="text-slate-500 text-sm">${person.status}</p>
268                        <p class="text-slate-500 text-sm">${person.education}</p>
269                        ${person.next ? `<p class="text-slate-500 text-sm">${person.next}</p>` : ''}
270                    </div>
271                    `;
272                }).join('');
273            }
274
275            function renderAlumniGroup() {
276                return `
277                    <div class="sm:col-span-2 lg:col-span-3 xl:col-span-4 pt-6 mt-3 border-t border-slate-200">
278                        <h3 class="text-2xl font-semibold text-center">Alumni</h3>
279                    </div>
280                    ${renderPersonCards(alumniData)}
281                `;
282            }
283
284            function renderPeople(filter = 'all') {
285                const container = document.getElementById('people-container');
286                if (!container) return;
287                const director = peopleData.find(p => p.role === 'Faculty');
288                const others = peopleData.filter(p => p.role !== 'Faculty');
289                const normalizedFilter = filter.toLowerCase().replace(/\./g, '');
290
291                let filteredPeople = others;
292                if (normalizedFilter === 'alumni') {
293                    filteredPeople = [];
294                } else if (normalizedFilter !== 'all') {
295                    filteredPeople = others.filter(p => p.role.toLowerCase().replace(/\./g, '') === normalizedFilter);
296                }
297
298                const directorNameHtml = director.website
299                    ? `<a href="${director.website}" target="_blank" rel="noopener noreferrer" class="news-link">${director.firstName} ${director.lastName}</a>`
300                    : `${director.firstName} ${director.lastName}`;
301
302                const directorHtml = `
303                    <div class="sm:col-span-2 lg:col-span-3 xl:col-span-4 mb-3">
304                        <div class="flex flex-col sm:flex-row items-center bg-white/95 border border-slate-200 rounded-xl shadow-sm p-5 sm:p-6">
305                            <img src="${director.photo}" alt="${director.firstName} ${director.lastName}" width="128" height="128" loading="lazy" decoding="async" class="w-32 h-32 rounded-full object-cover flex-shrink-0">
306                            <div class="sm:ml-6 mt-4 sm:mt-0 text-center sm:text-left">
307                                <h3 class="text-2xl font-semibold">${directorNameHtml}</h3>
308                                <p class="text-accent font-semibold">${director.status}</p>
309                                <p class="text-slate-600">${director.education}</p>
310                            </div>
311                        </div>
312                    </div>
313                `;
314
315                const alumniHtml = (normalizedFilter === 'all' || normalizedFilter === 'alumni')
316                    ? renderAlumniGroup()
317                    : '';
318
319                container.innerHTML = directorHtml + renderPersonCards(filteredPeople) + alumniHtml;
320            }
321
322            function renderPeopleFilters() {
323                const controlsContainer = document.getElementById('people-filter-control
323s');
324                if (!controlsContainer) return;
325                const roles = ['All', 'Postdoc Scholar', 'Ph.D. Student', 'MPhil Student', 'Intern', 'Alumni'];
326                controlsContainer.innerHTML = roles.map((role, index) => `
327                    <button class="filter-btn px-4 py-1.5 rounded-full text-sm ${index === 0 ? 'active' : ''}" data-filter="${role.toLowerCase().replace(/\./g, '')}">${role}</button>
328                `).join('');
329            }
330
331            function renderPublications(yearFilter = 'all', areaFilter = 'all') {
332                const container = document.getElementById('publications-container');
333                if (!container) return;
334
335                let filteredPubs;
336                if (yearFilter === 'preprint') {
337                    filteredPubs = publicationsData.filter(pub =>
338                        pub.type === 'Preprint' &&
339                        (areaFilter === 'all' || pub.area.includes(areaFilter))
340                    );
341                } else {
342                    filteredPubs = publicationsData.filter(pub =>
343                        (yearFilter === 'all' || pub.year == yearFilter) &&
344                        (areaFilter === 'all' || pub.area.includes(areaFilter))
345                    );
346                }
347
348                if (filteredPubs.length === 0) {
349                    container.innerHTML = `<p class="text-center text-slate-500">No publications match the selected filters.</p>`;
350                    return;
351                }
352
353                container.innerHTML = filteredPubs.map(pub => {
354                    const extraLinks = [
355                        pub.links.website ? `<a href="${pub.links.website}" class="publication-link" target="_blank" rel="noopener noreferrer">Website</a>` : '',
356                        pub.links.code ? `<a href="${pub.links.code}" class="publication-link" target="_blank" rel="noopener noreferrer">Code</a>` : '',
357                        pub.links.media ? `<a href="${pub.links.media}" class="publication-link" target="_blank" rel="noopener noreferrer">Media</a>` : '',
358                        pub.links.slides ? `<a href="${pub.links.slides}" class="publication-link" target="_blank" rel="noopener noreferrer">Slides</a>` : '',
359                        pub.links.video ? `<a href="${pub.links.video}" class="publication-link" target="_blank" rel="noopener noreferrer">Video</a>` : '',
360                        pub.links.data ? `<a href="${pub.links.data}" class="publication-link" target="_blank" rel="noopener noreferrer">Data</a>` : '',
361                        pub.links.model ? `<a href="${pub.links.model}" class="publication-link" target="_blank" rel="noopener noreferrer">Model</a>` : ''
362                    ].filter(Boolean);
363
364                    const extraLinksHtml = extraLinks.length > 0
365                        ? `<div class="mt-2 flex flex-wrap gap-x-3 gap-y-1 text-sm">${extraLinks.join('')}</div>`
366                        : '';
367
368                    const pdfActionHtml = pub.links.pdf
369                        ? `<a href="${pub.links.pdf}" class="publication-link pdf-action-link" target="_blank" rel="noopener noreferrer">PDF</a>`
370                        : '';
371
372                    return `
373                    <article ${pub.id ? `id="publication-${escapeHtml(pub.id)}" tabindex="-1"` : ''} class="publication-item ${pub.type === 'Preprint' ? 'preprint' : ''}">
374                        <div class="publication-meta">
375                            <span class="pub-badge">${pub.year}</span>
376                            <span class="pub-badge type">${pub.type}</span>
377                        </div>
378                        <div class="publication-body">
379                            ${pub.award ? `<p class="publication-award">${pub.award}</p>` : ''}
380                            <h3 class="font-semibold text-xl leading-tight">${pub.title}</h3>
381                            <p class="text-slate-600 mt-1 text-sm">${renderPublicationAuthors(pub.authors)}</p>
382                            <p class="text-slate-500 italic mt-1 text-sm">${pub.venue}</p>
383                            ${extraLinksHtml}
384                        </div>
385                        <div class="publication-actions">
386                            ${pdfActionHtml}
387                        </div>
388                    </article>
389                    `;
390                }).join('');
391            }
392
393            function populatePubFilters() {
394                const yearSelect = document.getElementById('pub-year-filter');
395                const areaSelect = document.getElementById('pub-area-filter');
396                if (!yearSelect || !areaSelect) return;
397
398                // Add Preprints option
399                const preprintOption = document.createElement('option');
400                preprintOption.value = 'preprint';
401                preprintOption.textContent = 'Preprints';
402                yearSelect.appendChild(preprintOption);
403
404                const years = [...new Set(publicationsData.filter(p => p.type !== 'Preprint').map(p => p.year))].sort((a, b) => b - a);
405                years.forEach(year => {
406                    const option = document.createElement('option');
407                    option.value = year;
408                    option.textContent = year;
409                    yearSelect.appendChild(option);
410                });
411
412                researchData.forEach(area => {
413                    const option = document.createElement('option');
414                    option.value = area.id;
415                    option.textContent = area.title;
416                    areaSelect.appendChild(option);
417                });
418            }
419
420            function renderNews() {
421                const container = document.getElementById('news-container');
422                if (!container) return;
423
424                container.innerHTML = newsData.map(item => {
425                    const titleHtml = item.url
426                        ? `<a href="${item.url}" class="news-link" target="_blank" rel="noopener noreferrer">${item.title}</a>`
427                        : item.title;
428
429                    return `
430                        <article class="news-item">
431                            <p class="news-date">${item.date}</p>
432                            <div>
433                                <h3 class="text-xl font-semibold leading-tight">${titleHtml}</h3>
434                                <p class="mt-1 text-slate-600 text-sm">${item.content}</p>
435                            </div>
436                        </article>
437                    `;
438                }).join('');
439            }
440
441
442            // Event Listeners
443            document.getElementById('mobile-menu-button').addEventListener('click', function () {
444                const menu = document.getElementById('mobile-menu');
445                const button = this;
446                const icons = button.querySelectorAll('svg');
447                menu.classList.toggle('hidden');
448                icons[0].classList.toggle('hidden');
449                icons[1].classList.toggle('hidden');
450            });
451
452            const sections = Array.from(document.querySelectorAll('section'));
453            const navLinks = Array.from(document.querySelectorAll('header a.nav-link'));
454
455            function setActiveNavLink(sectionId) {
456                navLinks.forEach(link => {
457                    const target = link.getAttribute('href');
458                    link.classList.toggle('active', Boolean(sectionId) && target === `#${sectionId}`);
459                });
460            }
461
462            function getCurrentSectionId() {
463                if (sections.length === 0) return '';
464                const probeY = Math.max(90, window.innerHeight * 0.24);
465
466                for (const section of sections) {
467                    const rect = section.getBoundingClientRect();
468                    if (rect.top <= probeY && rect.bottom > probeY) {
469                        return section.id;
470                    }
471                }
472
473                if (window.scrollY + window.innerHeight >= document.documentElement.scrollHeight - 4) {
474                    return sections[sections.length - 1].id;
475                }
476
477                return sections[0].id;
478            }
479
480            function syncActiveNavToViewport() {
481                setActiveNavLink(getCurrentSectionId());
482            }
483
484            document.querySelectorAll('#mobile-menu a, header nav a').forEach(link => {
485                link.addEventListener('click', () => {
486                    const sectionId = link.getAttribute('href').replace('#', '');
487                    setActiveNavLink(sectionId);
488
489                    if (link.closest('#mobile-menu')) {
490                        document.getElementById('mobile-menu').classList.add('hidden');
491                        const icons = document.getElementById('mobile-menu-button').querySelectorAll('svg');
492                        icons[0].classList.remove('hidden');
493                        icons[1].classList.add('hidden');
494                    }
495
496                    // Smooth scroll updates hash/position asynchronously; reconcile shortly after.
497                    window.setTimeout(syncActiveNavToViewport, 220);
498                });
499            });
500
501            document.getElementById('research-container').addEventListener('click', function (e) {
502                const selectedWorkLink = e.target.closest('[data-publication-id]');
503                if (selectedWorkLink) {
504                    document.getElementById('pub-year-filter').value = 'all';
505                    document.getElementById('pub-area-filter').value = 'all';
506                    renderPublications();
507                    return;
508                }
509                const publicationLink = e.target.closest('[data-research-area]');
510                if (publicationLink) {
511                    const area = publicationLink.dataset.researchArea;
512                    document.getElementById('pub-year-filter').value = 'all';
513                    document.getElementById('pub-area-filter').value = area;
514                    renderPublications('all', area);
515                    return;
516                }
517                if (e.target.closest('a')) return;
518
519                const card = e.target.closest('.research-card');
520                if (card) {
521                    const subtopicsId = `subtopics-${card.dataset.id}`;
522                    const subtopicsEl = document.getElementById(subtopicsId);
523                    const isExpanded = !subtopicsEl.classList.toggle('hidden');
524                    const toggle = card.querySelector('[data-research-toggle]');
525                    toggle.setAttribute('aria-expanded', String(isExpanded));
526                    toggle.querySelector('svg').classList.toggle('rotate-180', isExpanded);
527                    const container = document.getElementById('research-container');
528                    container.classList.toggle('items-start', Boolean(container.querySelector('[data-research-toggle][aria-expanded="true"]')));
529                }
530            });
531
532            document.getElementById('people-filter-controls').addEventListener('click', function (e) {
533                if (e.target.tagName === 'BUTTON') {
534                    document.querySelectorAll('#people-filter-controls .filter-btn').forEach(btn => btn.classList.remove('active'));
535                    e.target.classList.add('active');
536                    renderPeople(e.target.dataset.filter);
537                }
538            });
539
540            document.getElementById('pub-year-filter').addEventListener('change', () => {
541                renderPublications(document.getElementById('pub-year-filter').value, document.getElementById('pub-area-filter').value);
542            });
543
544            document.getElementById('pub-area-filter').addEventListener('change', () => {
545                renderPublications(document.getElementById('pub-year-filter').value, document.getElementById('pub-area-filter').value);
546            });
547
548            window.addEventListener('scroll', syncActiveNavToViewport, { passive: true });
549            window.addEventListener('resize', syncActiveNavToViewport);
550            window.addEventListener('hashchange', () => {
551                const hashSectionId = window.location.hash.replace('#', '');
552                if (hashSectionId) {
553                    setActiveNavLink(hashSectionId);
554                    window.setTimeout(syncActiveNavToViewport, 120);
555                }
556            });
557
558            // Initial Render
559            renderResearch();
560            renderPeopleFilters();
561            renderPeople();
562            populatePubFilters();
563            renderPublications();
564            renderNews();
565            syncActiveNavToViewport();
566        });
567    </script>
567
568</body>
569
570</html>

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.