Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added src/assets/tmc-ai-summit.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
116 changes: 64 additions & 52 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
---
import { SITE, LOGO_IMAGE } from '../config';
import {
import {
Menu, X, Globe, ChevronDown, Check, Moon, Sun,
Home, BookOpen, Trophy, Users, Calendar, UserPlus, Sparkles, Search
} from 'lucide-react';

import { ui, defaultLang, languages } from '../i18n/ui';

const { nav, i18n, customPages } = SITE;
const primaryNavItems = nav.filter((item) => item.key !== 'search');
const searchNavItem = nav.find((item) => item.key === 'search');

const iconMap: Record<string, any> = {
home: Home,
Expand Down Expand Up @@ -43,53 +45,51 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;

<!-- Desktop Nav -->
<nav class="hidden md:flex gap-6 items-center">
{nav.map(item => {
if (item.key === 'search') {
return (
<a
href={item.link}
class="p-2 text-gray-600 hover:text-blue-600 hover:bg-gray-50 rounded-full transition-colors"
aria-label={item.text}
>
<Search className="w-5 h-5" />
</a>
);
}
return (
<a
href={item.link}
class="text-sm font-medium text-gray-600 hover:text-blue-600 transition-colors"
data-i18n-key={`nav.${item.key}`}
>
{item.text}
</a>
);
})}

{primaryNavItems.map(item => (
<a
href={item.link}
class="text-sm font-medium text-gray-600 hover:text-blue-600 transition-colors"
data-i18n-key={`nav.${item.key}`}
>
{item.text}
</a>
))}

<!-- Custom Pages -->
{customPages && customPages.map(item => (
<a
href={item.link}
<a
href={item.link}
class="text-sm font-medium text-gray-600 hover:text-blue-600 transition-colors"
data-i18n-key={item.key}
data-i18n-key={item.key}
>
{item.text}
</a>
))}




{searchNavItem && (
<a
href={searchNavItem.link}
class="p-2 text-gray-600 hover:text-blue-600 hover:bg-gray-50 rounded-full transition-colors"
aria-label={searchNavItem.text}
data-i18n-key="nav.search"
>
<Search className="w-5 h-5" />
</a>
)}



{i18n.enabled && (
<div class="relative ml-2 group">
<button
<button
class="flex items-center gap-1 px-3 py-2 rounded-full bg-gray-50 hover:bg-gray-100 text-gray-600 transition-colors outline-none"
aria-label="Select Language"
>
<Globe className="w-4 h-4" />
<span class="text-xs font-medium uppercase" id="current-lang-label">ZH</span>
<ChevronDown className="w-3 h-3 opacity-50" />
</button>

<!-- Dropdown Menu -->
<div class="absolute right-0 pt-2 w-40 hidden group-hover:block transform origin-top-right transition-all">
<div class="bg-white rounded-xl shadow-xl border border-gray-100 py-2">
Expand All @@ -109,9 +109,9 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
</nav>

<!-- Mobile Menu Button -->
<button
id="mobile-menu-btn"
class="md:hidden p-2 -mr-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors z-[80] relative"
<button
id="mobile-menu-btn"
class="md:hidden p-2 -mr-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors z-[80] relative"
aria-label="Toggle menu"
aria-controls="mobile-menu"
aria-expanded="false"
Expand All @@ -128,8 +128,8 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
</div>

<!-- Mobile Nav Overlay (Solid White) -->
<div
id="mobile-menu"
<div
id="mobile-menu"
class="fixed inset-0 z-[100] w-screen h-screen bg-white md:hidden hidden flex-col"
style="display: none;"
role="dialog" aria-modal="true" aria-hidden="true" aria-labelledby="mobile-menu-title"
Expand All @@ -140,20 +140,20 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
{LOGO_IMAGE.enable && SITE.logo && <img src={SITE.logo} alt="Logo" class="h-10 w-10 object-contain" />}
<span class="font-bold text-gray-900 text-xl">{SITE.labName}</span>
</div>
<button
onclick="window.closeMobileMenu()"
<button
onclick="window.closeMobileMenu()"
class="p-2 -mr-2 rounded-full text-gray-600 hover:bg-gray-100 transition-colors"
aria-label="Close menu"
>
<X className="w-8 h-8" />
</button>
</div>

<!-- Scrollable Menu Content -->
<nav class="flex-1 overflow-y-auto px-6 py-8 flex flex-col gap-2 bg-white">
{nav.map((item) => (
<a
href={item.link}
{primaryNavItems.map((item) => (
<a
href={item.link}
class="flex items-center justify-between w-full py-4 px-4 text-lg font-medium text-gray-800 hover:text-blue-600 hover:bg-gray-50 rounded-xl transition-all border-b border-gray-100 last:border-0"
data-i18n-key={`nav.${item.key}`}
onclick="window.closeMobileMenu()"
Expand All @@ -162,11 +162,11 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
<ChevronDown className="w-5 h-5 -rotate-90 text-gray-400" />
</a>
))}

<!-- Custom Pages Mobile -->
{customPages && customPages.map((item) => (
<a
href={item.link}
<a
href={item.link}
class="flex items-center justify-between w-full py-4 px-4 text-lg font-medium text-gray-800 hover:text-blue-600 hover:bg-gray-50 rounded-xl transition-all border-b border-gray-100 last:border-0"
data-i18n-key={item.key}
onclick="window.closeMobileMenu()"
Expand All @@ -176,6 +176,18 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
</a>
))}

{searchNavItem && (
<a
href={searchNavItem.link}
class="flex items-center justify-between w-full py-4 px-4 text-lg font-medium text-gray-800 hover:text-blue-600 hover:bg-gray-50 rounded-xl transition-all border-b border-gray-100 last:border-0"
data-i18n-key="nav.search"
onclick="window.closeMobileMenu()"
>
<span>{searchNavItem.text}</span>
<ChevronDown className="w-5 h-5 -rotate-90 text-gray-400" />
</a>
)}

{i18n.enabled && (
<div class="mt-8 pt-6 border-t border-gray-100">
<div class="text-sm font-bold text-gray-500 mb-4 uppercase tracking-wider px-2">Language</div>
Expand Down Expand Up @@ -247,7 +259,7 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
// Force reflow
menu.offsetHeight;
menu.classList.remove('hidden');

// Add simple fade in
menu.animate([
{ opacity: 0 },
Expand All @@ -258,11 +270,11 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
easing: 'ease-out'
});
}

// Animate hamburger to X
iconMenu?.classList.add('rotate-90', 'opacity-0');
iconClose?.classList.remove('hidden', 'rotate-90');

// Lock body scroll
document.body.classList.add('overflow-hidden');
setAria(true);
Expand All @@ -271,7 +283,7 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
// Focus the first link
const firstLink = menu?.querySelector('a');
firstLink?.focus?.();

document.addEventListener('keydown', onKeydown);
}

Expand All @@ -286,13 +298,13 @@ const getIcon = (key: string) => iconMap[key] || Sparkles;
fill: 'forwards',
easing: 'ease-in'
});

animation.onfinish = () => {
menu.style.display = 'none';
menu.classList.add('hidden');
};
}

// Animate X back to hamburger
iconMenu?.classList.remove('rotate-90', 'opacity-0');
iconClose?.classList.add('rotate-90');
Expand Down
12 changes: 12 additions & 0 deletions src/content/activities/2026-tmc-ai-summit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: "TMC AI Summit 2026"
date: 2026-02-18
cover: "../../assets/tmc-ai-summit.jpg"
description: "The TMC AI Summit 2026 is upcoming, with Dr. Zhandong Liu serving on the Steering Committee, Dr. Hyun-Hwan Jeong serving as Student Research Showcase Chair, and lab members presenting their work."
---

The [TMC AI Summit 2026](https://tmc-ai-summit.org/) is upcoming and will bring together researchers, clinicians, and industry partners to discuss advances in AI for health and biomedicine.

Dr. Zhandong Liu will serve on the **Steering Committee**, and Dr. Hyun-Hwan Jeong will serve as the **Student Research Showcase Chair**.

A couple of lab members will also present their research at the summit.