// AboutSection — hero identity block: name, role @ university, social // buttons, profile portrait, bio prose, and a research-area chip row. const { Button: AButton, Badge: ABadge } = window.WINTELAcademicDesignSystem_045d4f; function AboutSection({ data, onNavigate }) { const p = data.person; return ( {onNavigate ? ( onNavigate('contact')} style={{ background: '#ea541f', borderColor: '#ea541f', color: '#ffffff' }}> Apply ) : null} {p.name} {p.role} {p.affiliations.map((a, i) => ( {a.prefix || ''} {a.url ? ( {a.text} ) : a.text} ))} 4005 Mountbatten Bdg. (53), Salisbury Rd., Southampton SO17 1BJ, UK Map {p.social.map((s) => ( ))} {p.bio} {p.bio2 ? {p.bio2} : null} ); } // BlogShowcase — auto-advancing slideshow of recent blog posts. Pulls every // non-draft post that has a lead image from data.blog, so adding a new post // (with a figure) automatically appears here. "Read more" opens that exact // post in the Research view. function leadImage(post) { const fig = (post.body || []).find((b) => b && b.fig); if (!fig) return null; return (fig.resId && window.__resources && window.__resources[fig.resId]) || fig.fig; } function BlogShowcase({ data, onNavigate }) { const slides = (data.blog || []) .filter((p) => !p.draft && leadImage(p)) .map((p) => ({ post: p, img: leadImage(p) })); const [i, setI] = React.useState(0); const [paused, setPaused] = React.useState(false); const n = slides.length; React.useEffect(() => { if (paused || n <= 1) return undefined; const id = setInterval(() => setI((v) => (v + 1) % n), 5000); return () => clearInterval(id); }, [paused, n]); if (!n) return null; const go = (idx) => setI((idx + n) % n); const openPost = (post) => { window.__researchOpen = post.title; if (onNavigate) onNavigate('research'); }; return ( setPaused(true)} onMouseLeave={() => setPaused(false)} style={{ display: 'flex', flexDirection: 'column', gap: '0.85rem' }}> From the Research Blog onNavigate && onNavigate('research')} style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', color: 'var(--accent)' }}> All posts {slides.map((s, idx) => ( {s.post.tag} {s.post.date} {s.post.title} openPost(s.post)} style={{ flexShrink: 0, display: 'inline-flex', alignItems: 'center', gap: '0.4rem', padding: '0.5rem 0.95rem', borderRadius: 'var(--radius-md)', border: '1px solid rgba(255,255,255,0.35)', background: 'rgba(255,255,255,0.08)', backdropFilter: 'blur(4px)', color: '#fff', fontSize: 'var(--text-sm)', fontWeight: 'var(--weight-medium)', cursor: 'pointer', whiteSpace: 'nowrap' }}> Read more ))} {n > 1 ? ( go(i - 1)} style={{ position: 'absolute', top: '50%', left: '0.6rem', transform: 'translateY(-50%)', width: '2rem', height: '2rem', borderRadius: '999px', border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(7,9,12,0.45)', color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}> go(i + 1)} style={{ position: 'absolute', top: '50%', right: '0.6rem', transform: 'translateY(-50%)', width: '2rem', height: '2rem', borderRadius: '999px', border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(7,9,12,0.45)', color: '#fff', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}> {slides.map((_, idx) => ( go(idx)} style={{ width: idx === i ? '1.4rem' : '0.5rem', height: '0.5rem', borderRadius: '999px', border: 'none', padding: 0, cursor: 'pointer', background: idx === i ? '#fff' : 'rgba(255,255,255,0.45)', transition: 'width 300ms var(--ease-standard)' }} /> ))} ) : null} ); } Object.assign(window, { AboutSection });
{a.prefix || ''} {a.url ? ( {a.text} ) : a.text}
{p.bio}
{p.bio2}