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
9 changes: 9 additions & 0 deletions about/our-team.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
id: our-team
title: Our Team
hide_table_of_contents: true
---

import TeamDirectory from '../src/components/TeamDirectory';

<TeamDirectory />
5 changes: 5 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ module.exports = {
href:'/about/stack'
},

{
label: "Our Team",
href:'/about/our-team'
},

],
},

Expand Down
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module.exports = {
'ekilab',
'ekimetrics',
'stack',
'our-team',
],

},
Expand Down
109 changes: 109 additions & 0 deletions src/components/TeamDirectory/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
{
"people": [
{
"id": "duong_nguyen",
"name": "Duong Nguyen",
"role": "Senior AI Research Lead",
"email": "duong.nguyen@ekimetrics.com",
"tags": ["Time Series", "Forecasting", "Tabular Foundation Models"]
Comment on lines +4 to +8
},
{
"id": "nicolas_chesneau",
"name": "Nicolas Chesneau",
"role": "Director of Research",
"email": "nicolas.chesneau@ekimetrics.com",
"tags": ["Computer Vision", "NLP", "GenAI"]
},
{
"id": "jean_lelong",
"name": "Jean Lelong",
"role": "Senior Ai Researcher",
"email": "jean.lelong@ekimetrics.com",
Comment on lines +19 to +21
"tags": ["NLP", "GenAI"]
},
{
"id": "yedidia_agnimo",
"name": "Yedidia Agnimo",
"role": "Doctoral Researcher",
"email": "yedidia.agnimo@ekimetrics.com",
"tags": ["NLP", "GenAI", "LLM Hallucinations"]
},
{
"id": "yann_choho",
"name": "Yann Choho",
"role": "Doctoral Researcher",
"email": "yann.choho@ekimetrics.com",
"tags": ["NLP"]
},
{
"id": "francois_guerillon",
"name": "François Guerillon",
"role": "Senior AI Researcher",
"email": "francois.guerillon@ekimetrics.com",
"tags": ["Deep Learning", "Model Fine-tuning", "Computer Vision", "Multimodality"]
},
{
"id": "andrew_mcdonald",
"name": "Andrew McDonald",
"role": "Doctoral Researcher",
"email": "andrew.mcdonald@ekimetrics.com",
"tags": ["Tabular Foundation Models", "Forecasting"]
},
{
"id": "sami_achour",
"name": "Sami Achour",
"role": "Senior AI Scientist",
"email": "sami.achour@ekimetrics.com",
"tags": ["Time Series", "Forecasting"]
},
{
"id": "audrey_poinsot",
"name": "Audrey Poinsot",
"role": "Senior AI Researcher",
"email": "audrey.poinsot@ekimetrics.com",
"tags": ["Causal Inference"]
},
{
"id": "milan_bhan",
"name": "Milan Bhan",
"role": "Senior AI Researcher",
"email": "milan.bhan@ekimetrics.com",
"tags": ["Interpretability", "NLP"]
},
{
"id": "victoria_danon",
"name": "Victoria Danon",
"role": "Senior Data Scientist",
"email": "victoria.danon@ekimetrics.com",
"tags": ["NLP", "GenAI"]
},
{
"id": "pierre_moreau",
"name": "Pierre Moreau",
"role": "Senior Data Scientist",
"email": "pierre.moreau@ekimetrics.com",
"tags": ["Interpretability"]
},
{
"id": "benjamin_wong",
"name": "Benjamin Wong",
"role": "Senior Data Scientist",
"email": "benjamin.wong@ekimetrics.com",
"tags": []
},
{
"id": "annabelle_blangero",
"name": "Annabelle Blangero",
"role": "Senior AI Research Lead",
"email": "annabelle.blangero@ekimetrics.com",
"tags": ["NLP", "GenAI", "AI Governance"]
},
{
"id": "toni_fortora",
"name": "Toni Fortora",
"role": "Senior Data Scientist",
"email": "toni.fortora@ekimetrics.com",
"tags": ["Causal Inference"]
}
]
}
118 changes: 118 additions & 0 deletions src/components/TeamDirectory/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React, { useState, useMemo } from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
import data from './data.json';

const AVATAR_COLORS = [
'#1a6b8a', '#2d7a4f', '#7a3d8a', '#8a5c1a',
'#1a4a8a', '#8a1a3d', '#3d8a1a', '#8a6b1a',
];

function avatarColor(name) {
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return AVATAR_COLORS[h % AVATAR_COLORS.length];
}

function initials(name) {
return name
.split(' ')
.filter(Boolean)
.slice(0, 2)
.map((w) => w[0].toUpperCase())
.join('');
}

function EmailIcon() {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<rect x="2" y="4" width="20" height="16" rx="2" />
<polyline points="2,4 12,13 22,4" />
</svg>
);
}

function collectTags(people) {
const set = new Set();
for (const p of people) for (const t of p.tags) set.add(t);
return [...set].sort();
}

function PersonCard({ person, activeTags, onToggleTag }) {
return (
<div className={styles.personCard}>
<div className={styles.personAvatar} style={{ background: avatarColor(person.name) }}>
{initials(person.name)}
</div>
<div className={styles.personBody}>
<h2 className={styles.personName}>{person.name}</h2>
<p className={styles.personRole}>{person.role}</p>
<div className={styles.personTags}>
{person.tags.map((tag) => (
<span
key={tag}
className={clsx(styles.personTag, activeTags.has(tag) && styles.personTagActive)}
onClick={() => onToggleTag(tag)}
>
Comment on lines +52 to +56
{tag}
</span>
))}
</div>
</div>
<a className={styles.personEmail} href={`mailto:${person.email}`} title={person.email}>
<EmailIcon />
</a>
</div>
);
}

export default function TeamDirectory() {
const [activeTags, setActiveTags] = useState(new Set());
const allTags = useMemo(() => collectTags(data.people), []);

const toggleTag = (tag) => {
setActiveTags((prev) => {
const next = new Set(prev);
if (next.has(tag)) next.delete(tag);
else next.add(tag);
return next;
});
};

const visiblePeople = useMemo(() => {
if (activeTags.size === 0) return data.people;
return data.people.filter((p) => [...activeTags].every((t) => p.tags.includes(t)));
}, [activeTags]);

return (
<div className={clsx('container', styles.teamPage)}>
<div className={styles.teamHero}>
<h1>Our Team</h1>
<p>Meet the people behind Eki.Lab</p>
</div>

<div className={styles.tagFilterBar}>
<span className={styles.tagFilterBarLabel}>Filter:</span>
{allTags.map((tag) => (
<span
key={tag}
className={clsx(styles.filterTag, activeTags.has(tag) && styles.filterTagActive)}
onClick={() => toggleTag(tag)}
>
Comment on lines +97 to +101
{tag}
</span>
))}
</div>

<div className={styles.teamGrid}>
{visiblePeople.length === 0 ? (
<div className={styles.teamEmpty}>No team members match the selected filters.</div>
) : (
visiblePeople.map((person) => (
<PersonCard key={person.id} person={person} activeTags={activeTags} onToggleTag={toggleTag} />
))
)}
</div>
</div>
);
}
Loading
Loading