Team
The people behind the product: faces, names, roles and where to find them.
Team portraits
team-01Eight people as portraits — a face on a tile of the muted plane, then the name, the role and the office — two to a row on a phone, four from lg.
import * as React from "react"
import { MapPinIcon } from "lucide-react"
import { avatarFor } from "@/lib/avatars"
import { getInitials } from "@/lib/format"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { EYEBROW, HEADLINE, LEDE, PEOPLE, type Person } from "./team-01.data"
/**
* One person as a portrait: their face on a tile of the muted plane — the
* plane the face is drawn on, so in the light theme the face sits on the tile
* itself rather than in a disc — then their name, their role and the office
* they work from. The face is decoration; the name under it says who it is.
*/
function Portrait({ person }: { person: Person }) {
return (
<li className="flex flex-col">
<div className="flex aspect-4/3 items-center justify-center rounded-xl bg-muted">
<Avatar className="size-20 after:hidden sm:size-28 lg:size-32">
<AvatarImage src={avatarFor(person.name)} alt="" />
<AvatarFallback className="text-xl">{getInitials(person.name)}</AvatarFallback>
</Avatar>
</div>
<h3 className="mt-4 text-base font-semibold">{person.name}</h3>
<p className="mt-0.5 text-sm text-pretty text-muted-foreground">{person.role}</p>
<p className="mt-2 flex items-center gap-1.5 text-xs text-muted-foreground">
<MapPinIcon aria-hidden="true" className="size-3.5 shrink-0" />
{person.city}
</p>
</li>
)
}
/**
* The team as a grid of portraits: eight people, each a face on a tile with
* their name, role and office under it — two to a row on a phone, four from
* `lg`, so the grid always divides evenly. Each name is an h3, so a screen
* reader can move through the team by heading.
*
* A server component; every word is in `./team-01.data`.
*/
export function Team01() {
const titleId = React.useId()
return (
<section
data-section="team-01"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto max-w-7xl">
<div className="max-w-2xl">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
</div>
<ul className="mt-10 grid grid-cols-2 gap-x-4 gap-y-8 md:mt-12 lg:grid-cols-4 lg:gap-x-6 lg:gap-y-12">
{PEOPLE.map((person) => (
<Portrait key={person.name} person={person} />
))}
</ul>
</div>
</section>
)
}
export default Team01/**
* Every word `team-01` sets: eight of the people who build Northwind, each
* with their role and the office they work from. The people are fictional,
* and each gets the kit's face for their name; replace them with your own
* team, and ask each person before their name goes on the page.
*/
export type Person = { name: string; role: string; city: string }
export const EYEBROW = "Team"
export const HEADLINE = "The people who build Northwind"
export const LEDE =
"Thirty-two of us across London, Lisbon and Toronto. These are the eight you are most likely to meet in your first month as a customer."
export const PEOPLE: readonly Person[] = [
{ name: "Ines Moreau", role: "Co-founder and CEO", city: "London" },
{ name: "Tomasz Wójcik", role: "Co-founder and CTO", city: "Lisbon" },
{ name: "Adaeze Nwosu", role: "VP Engineering", city: "London" },
{ name: "Rafael Duarte", role: "Head of Product", city: "Lisbon" },
{ name: "Leila Haddad", role: "Head of Design", city: "London" },
{ name: "Kenji Arakawa", role: "Head of Data", city: "London" },
{ name: "Signe Halvorsen", role: "Head of Customer Success", city: "Toronto" },
{ name: "Grace O'Connell", role: "Finance Lead", city: "London" },
]npx shadcn@latest add @vibra/team-01Leadership with bios
team-02The leaders in rows divided by rules: each a face beside the name and role, what they answer for, where they worked before and an address to write to.
import * as React from "react"
import { MailIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { avatarFor } from "@/lib/avatars"
import { getInitials } from "@/lib/format"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { buttonVariants } from "@/components/ui/button"
import { LogoMark } from "@/components/ui/logo-mark"
import { BEFORE_LABEL, EYEBROW, HEADLINE, LEADERS, LEDE, type Leader } from "./team-02.data"
/**
* One leader: their face beside their name and role, then what they are
* accountable for in a few sentences, where they worked before — the
* company's mark, which names it — and their own address, shown and linked.
*/
function LeaderRow({ leader }: { leader: Leader }) {
return (
<li className="flex gap-5 border-t border-rule py-8 sm:gap-6 md:py-10">
<Avatar className="size-16 sm:size-20">
<AvatarImage src={avatarFor(leader.name)} alt="" />
<AvatarFallback className="text-lg">{getInitials(leader.name)}</AvatarFallback>
</Avatar>
<div className="min-w-0 flex-1">
<h3 className="text-lg font-semibold">{leader.name}</h3>
<p className="text-sm text-muted-foreground">{leader.role}</p>
<p className="mt-4 text-sm/6 text-pretty text-muted-foreground">{leader.bio}</p>
<div className="mt-5 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between sm:gap-x-6">
<p className="flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-muted-foreground">
<span>
{BEFORE_LABEL}
<span className="sr-only">:</span>
</span>
<LogoMark name={leader.before.company} size="sm" className="text-foreground" />
<span>
<span className="sr-only">, </span>
{leader.before.role}
</span>
</p>
<a
href={`mailto:${leader.email}`}
className={cn(buttonVariants({ variant: "link" }), "h-auto self-start px-0 sm:self-auto")}
>
<MailIcon aria-hidden="true" />
{leader.email}
</a>
</div>
</div>
</li>
)
}
/**
* The leadership in rows divided by rules, two to a row from `lg`: each a
* face beside the name, the role and a short account of what they answer
* for, where they worked before and an address a reader can write to —
* no social marks, because the kit draws none and an address is the one
* link that reaches the person. Each name is an h3.
*
* A server component; every word is in `./team-02.data`.
*/
export function Team02() {
const titleId = React.useId()
return (
<section
data-section="team-02"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto max-w-7xl">
<div className="max-w-2xl">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
</div>
<ul className="mt-10 grid md:mt-12 lg:grid-cols-2 lg:gap-x-16">
{LEADERS.map((leader) => (
<LeaderRow key={leader.name} leader={leader} />
))}
</ul>
</div>
</section>
)
}
export default Team02/**
* Every word `team-02` sets: the four people who lead Northwind — what each
* is accountable for, where they worked before and how to write to them. The
* people are fictional, and each gets the kit's face for their name; the
* companies they came from are the kit's fictional ones, drawn by `LogoMark`.
* Replace them with your own leadership, in words they approved.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type Leader = {
name: string
role: string
bio: string
/** Where they worked before, and as what. */
before: { company: LogoName; role: string }
/** Their own address, shown and linked: a reader writes to the person. */
email: string
}
export const EYEBROW = "Leadership"
export const HEADLINE = "The four people accountable for Northwind"
export const LEDE =
"Each of them did the work our customers do before building the product that does it. Write to any of them, and expect a reply from the person, not a queue."
/** The label before a leader's previous company. */
export const BEFORE_LABEL = "Before Northwind"
export const LEADERS: readonly Leader[] = [
{
name: "Ines Moreau",
role: "Co-founder and CEO",
bio: "Ran finance operations at a payments company for eight years, and started Northwind after one month-end too many that finished at two in the morning. Leads sales, hiring and the board.",
before: { company: "Brightwater", role: "Head of Finance Operations" },
email: "ines@northwind.example",
},
{
name: "Tomasz Wójcik",
role: "Co-founder and CTO",
bio: "Built the reporting engine behind a travel company's finance team of forty. Owns the query engine that runs every Northwind report, and still reviews each change that touches it.",
before: { company: "Kestrel", role: "Principal Engineer" },
email: "tomasz@northwind.example",
},
{
name: "Adaeze Nwosu",
role: "VP Engineering",
bio: "Grew engineering from four people to fifteen without adding a single status meeting. Runs the on-call rota, and takes her turn on it like everyone else.",
before: { company: "Keystone", role: "Director of Infrastructure" },
email: "adaeze@northwind.example",
},
{
name: "Rafael Duarte",
role: "Head of Product",
bio: "Spent six years as a controller before he wrote his first spec, so the product speaks the language of a close. Talks to five customers every week, and brings their words to the roadmap.",
before: { company: "Copperline", role: "Financial Controller" },
email: "rafael@northwind.example",
},
]npx shadcn@latest add @vibra/team-02Directory by office
team-03Everyone at the company as a compact directory, one column per office, each headed by the city, its headcount and its time zone.
import * as React from "react"
import { avatarFor } from "@/lib/avatars"
import { UserCell } from "@/components/ui/user-cell"
import { EYEBROW, HEADLINE, LEDE, OFFICES, headcount, type Office } from "./team-03.data"
/**
* One office: its city as an h3, how many people work there and its time
* zone, then its people as a list named by the city, each row the kit's
* UserCell — the face, the name and the role on one compact line pair.
*/
function OfficeList({ office }: { office: Office }) {
const headingId = React.useId()
return (
<div>
<div className="flex items-baseline justify-between gap-4 border-b border-rule pb-3">
<h3 id={headingId} className="text-base font-semibold">
{office.city}
</h3>
<p className="text-xs text-muted-foreground tabular-nums">
{headcount(office.people.length)} · {office.timezone}
</p>
</div>
<ul aria-labelledby={headingId} className="divide-y divide-border">
{office.people.map((person) => (
<li key={person.name} className="py-3">
<UserCell name={person.name} description={person.role} src={avatarFor(person.name)} />
</li>
))}
</ul>
</div>
)
}
/**
* The whole company as a compact directory, filed by office: one column per
* city from `md`, each headed by the city, its headcount and its time zone,
* then its people in rows of the kit's UserCell. A reader looking for a
* person scans a column; one looking for a time zone reads the heads. Each
* office's list is named by its h3.
*
* A server component; every word is in `./team-03.data`.
*/
export function Team03() {
const titleId = React.useId()
return (
<section
data-section="team-03"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto max-w-7xl">
<div className="max-w-2xl">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
</div>
<div className="mt-10 grid gap-10 md:mt-12 md:grid-cols-3 md:gap-8 lg:gap-12">
{OFFICES.map((office) => (
<OfficeList key={office.city} office={office} />
))}
</div>
</div>
</section>
)
}
export default Team03/**
* Every word `team-03` sets: everyone at Northwind, filed under the office
* they work from, each with their role. The people are fictional, and each
* gets the kit's face for their name; replace them with your own team, and
* keep each office's list in the order you want it read.
*/
export type Colleague = { name: string; role: string }
export type Office = {
city: string
/** The office's own time, so a reader knows when a person is at their desk. */
timezone: string
people: readonly Colleague[]
}
export const EYEBROW = "Directory"
export const HEADLINE = "Everyone at Northwind, by office"
export const LEDE =
"Thirty-two people in three offices. Everyone takes a support shift once a quarter, so the name on your reply may well be on this list."
/** How an office's head counts its people: "14 people". */
export function headcount(count: number): string {
return `${count} ${count === 1 ? "person" : "people"}`
}
export const OFFICES: readonly Office[] = [
{
city: "London",
timezone: "GMT",
people: [
{ name: "Adaeze Nwosu", role: "VP Engineering" },
{ name: "Ayo Okafor", role: "Data Scientist, Alerts" },
{ name: "Callum Reid", role: "Design Engineer" },
{ name: "Elliot Mercer", role: "Technical Writer" },
{ name: "Freya Lindqvist", role: "Frontend Engineer" },
{ name: "Grace O'Connell", role: "Finance Lead" },
{ name: "Ines Moreau", role: "Co-founder and CEO" },
{ name: "Kenji Arakawa", role: "Head of Data" },
{ name: "Kofi Boateng", role: "Support Engineer" },
{ name: "Leila Haddad", role: "Head of Design" },
{ name: "Maya Goldberg", role: "Solutions Consultant" },
{ name: "Oskar Nilsson", role: "Security Engineer" },
{ name: "Samuel Asante", role: "Site Reliability Engineer" },
{ name: "Yusuf Demir", role: "People Operations Lead" },
],
},
{
city: "Lisbon",
timezone: "WET",
people: [
{ name: "Astrid Solberg", role: "Analytics Engineer" },
{ name: "Beatriz Costa", role: "Engineer, Alerts" },
{ name: "Lucía Ferreira", role: "Onboarding Lead" },
{ name: "Mateo Rinaldi", role: "Engineer, Integrations" },
{ name: "Noor Siddiqui", role: "Product Designer" },
{ name: "Paula Novak", role: "Workplace Lead" },
{ name: "Priyanka Bose", role: "Staff Engineer, Query engine" },
{ name: "Rafael Duarte", role: "Head of Product" },
{ name: "Rui Tavares", role: "Data Engineer" },
{ name: "Tomasz Wójcik", role: "Co-founder and CTO" },
],
},
{
city: "Toronto",
timezone: "ET",
people: [
{ name: "Arjun Menon", role: "Engineer, Billing connectors" },
{ name: "Chloé Martin", role: "Customer Success Manager" },
{ name: "Daniel Mbeki", role: "Support Engineer" },
{ name: "Hana Kobayashi", role: "Product Manager, Reporting" },
{ name: "Hye-jin Park", role: "Engineer, Forecasts" },
{ name: "Ruth Achebe", role: "Account Executive" },
{ name: "Signe Halvorsen", role: "Head of Customer Success" },
{ name: "Tariq Hassan", role: "Engineer, Exports" },
],
},
]npx shadcn@latest add @vibra/team-03Team by department
team-04A row of department chips over a grid of people: choose a department and only its people show, each tab saying how many it holds.
import * as React from "react"
import { MapPinIcon } from "lucide-react"
import { avatarFor } from "@/lib/avatars"
import { getInitials } from "@/lib/format"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { DepartmentTabs } from "./team-04.client"
import {
DEPARTMENTS,
EVERYONE,
EYEBROW,
FILTER_LABEL,
HEADLINE,
LEDE,
MEMBERS,
inDepartment,
peopleCount,
type Member,
} from "./team-04.data"
/** One person's card: the face, the name as an h3, the role and the office. */
function MemberCard({ member }: { member: Member }) {
return (
<li className="flex flex-col items-center panel px-4 py-6 text-center">
<Avatar className="size-16">
<AvatarImage src={avatarFor(member.name)} alt="" />
<AvatarFallback className="text-base">{getInitials(member.name)}</AvatarFallback>
</Avatar>
<h3 className="mt-4 text-sm font-semibold">{member.name}</h3>
<p className="mt-0.5 text-xs text-balance text-muted-foreground">{member.role}</p>
<p className="mt-2 flex items-center gap-1 text-xs text-muted-foreground">
<MapPinIcon aria-hidden="true" className="size-3.5 shrink-0" />
{member.city}
</p>
</li>
)
}
function MemberGrid({ members }: { members: readonly Member[] }) {
return (
<ul className="grid grid-cols-2 gap-3 md:grid-cols-4 lg:gap-4">
{members.map((member) => (
<MemberCard key={member.name} member={member} />
))}
</ul>
)
}
/**
* The team, filtered by department: a row of chips — everyone, then each
* department with how many people it holds — over a grid of cards, each a
* face, a name, a role and an office. Two cards to a row on a phone, four
* from `md`.
*
* Every department's grid is drawn here from `inDepartment()`, the same rule
* that counts it, and handed to the island (`./team-04.client`), which makes
* the chips the kit's Tabs. Every word is in `./team-04.data`.
*/
export function Team04() {
const titleId = React.useId()
const groups = [
{ value: "everyone", label: EVERYONE, members: MEMBERS },
...DEPARTMENTS.map((department) => ({
value: department.id,
label: department.label,
members: inDepartment(MEMBERS, department.id),
})),
]
const tabs = groups.map((group) => ({
value: group.value,
label: group.label,
count: group.members.length,
countLabel: peopleCount(group.members.length),
panel: <MemberGrid members={group.members} />,
}))
return (
<section
data-section="team-04"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto max-w-7xl">
<div className="max-w-2xl">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
</div>
<div className="mt-10 md:mt-12">
<DepartmentTabs tabs={tabs} label={FILTER_LABEL} />
</div>
</div>
</section>
)
}
export default Team04"use client"
import * as React from "react"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
/** One department as the island receives it: its name, how many people it holds, and their cards, drawn by the server file. */
export type DepartmentTab = {
value: string
label: string
count: number
/** The count in words, for a screen reader: "5 people". */
countLabel: string
panel: React.ReactNode
}
/**
* The department filter is the kit's Tabs: one choice drives one grid. The
* arrow keys move between departments and show each one's people as they
* land, and every tab says how many people it holds — in figures to the eye
* and in words to a screen reader ("Engineering, 5 people").
*
* The chosen department is a chip on the accent's tint at the 500 weight,
* never a stroke; the others carry the hairline so they read as chips before
* anyone hovers. Every card arrives drawn, as plain props, from the section.
*/
export function DepartmentTabs({ tabs, label }: { tabs: readonly DepartmentTab[]; label: string }) {
return (
<Tabs defaultValue={tabs[0]?.value} className="gap-0">
<TabsList
variant="line"
aria-label={label}
activateOnFocus
className="w-full flex-wrap justify-start gap-2 p-0 group-data-horizontal/tabs:h-auto"
>
{tabs.map((tab) => (
<TabsTrigger
key={tab.value}
value={tab.value}
className="h-8 flex-none gap-2 rounded-full px-3 font-normal ring-1 ring-border after:hidden hover:bg-muted/50 data-active:bg-brand-muted data-active:font-medium data-active:ring-transparent data-active:hover:bg-brand-muted"
>
{tab.label}
<span aria-hidden="true" className="text-muted-foreground tabular-nums">
{tab.count}
</span>
<span className="sr-only">, {tab.countLabel}</span>
</TabsTrigger>
))}
</TabsList>
{tabs.map((tab) => (
<TabsContent key={tab.value} value={tab.value} className="mt-8 rounded-xl focus-ring md:mt-10">
{tab.panel}
</TabsContent>
))}
</Tabs>
)
}/**
* Every word `team-04` sets: sixteen people and the departments they are
* filed under, and the words of the filter that picks between them. The
* people are fictional, and each gets the kit's face for their name; replace
* them with your own team. `inDepartment()` is the one rule the filter
* follows, so a department's count and its people can never disagree.
*/
export type DepartmentId = "engineering" | "product" | "data" | "customers"
export type Department = { id: DepartmentId; label: string }
export type Member = { name: string; role: string; city: string; department: DepartmentId }
export const EYEBROW = "Team"
export const HEADLINE = "Meet the team, one department at a time"
export const LEDE =
"Sixteen of the people you will work with as a customer, filed by what they do. Choose a department to see only its people."
/** Names the row of departments, which is a set of tabs. */
export const FILTER_LABEL = "Show people from"
/** The tab that shows everyone. */
export const EVERYONE = "Everyone"
/** What a department's count is read as: "5 people". */
export function peopleCount(count: number): string {
return `${count} ${count === 1 ? "person" : "people"}`
}
export const DEPARTMENTS: readonly Department[] = [
{ id: "engineering", label: "Engineering" },
{ id: "product", label: "Product and design" },
{ id: "data", label: "Data" },
{ id: "customers", label: "Customer success" },
]
/** The members of one department, in the order the data gives them. */
export function inDepartment(members: readonly Member[], department: DepartmentId): Member[] {
return members.filter((member) => member.department === department)
}
export const MEMBERS: readonly Member[] = [
{ name: "Adaeze Nwosu", role: "VP Engineering", city: "London", department: "engineering" },
{ name: "Priyanka Bose", role: "Staff Engineer, Query engine", city: "Lisbon", department: "engineering" },
{ name: "Mateo Rinaldi", role: "Engineer, Integrations", city: "Lisbon", department: "engineering" },
{ name: "Hye-jin Park", role: "Engineer, Forecasts", city: "Toronto", department: "engineering" },
{ name: "Samuel Asante", role: "Site Reliability Engineer", city: "London", department: "engineering" },
{ name: "Rafael Duarte", role: "Head of Product", city: "Lisbon", department: "product" },
{ name: "Leila Haddad", role: "Head of Design", city: "London", department: "product" },
{ name: "Noor Siddiqui", role: "Product Designer", city: "Lisbon", department: "product" },
{ name: "Hana Kobayashi", role: "Product Manager, Reporting", city: "Toronto", department: "product" },
{ name: "Kenji Arakawa", role: "Head of Data", city: "London", department: "data" },
{ name: "Ayo Okafor", role: "Data Scientist, Alerts", city: "London", department: "data" },
{ name: "Astrid Solberg", role: "Analytics Engineer", city: "Lisbon", department: "data" },
{ name: "Signe Halvorsen", role: "Head of Customer Success", city: "Toronto", department: "customers" },
{ name: "Lucía Ferreira", role: "Onboarding Lead", city: "Lisbon", department: "customers" },
{ name: "Daniel Mbeki", role: "Support Engineer", city: "Toronto", department: "customers" },
{ name: "Chloé Martin", role: "Customer Success Manager", city: "Toronto", department: "customers" },
]npx shadcn@latest add @vibra/team-04Team with open roles
team-05The open roles in a framed card, each naming the person it reports to by their face, beside the team a new hire would join and what working there comes with.
import * as React from "react"
import Link from "next/link"
import { ArrowRightIcon, CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { avatarFor } from "@/lib/avatars"
import { getInitials } from "@/lib/format"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { AvatarGroup } from "@/components/ui/avatar-group"
import { Badge } from "@/components/ui/badge"
import { buttonVariants } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import {
EYEBROW,
FACES_SHOWN,
HEADLINE,
HOW_WE_HIRE,
LEDE,
ROLES,
ROLES_TITLE,
TEAM,
TERMS,
reportsTo,
teamCaption,
type Role,
} from "./team-05.data"
/**
* One opening as a row: the title — a link stretched over the row, so the
* row is one stop in the tab order — its team and where it can be done from,
* and the face and first name of the person it reports to. The row hovers on
* half the muted plane, the way a table row does, and draws the focus ring
* inside itself, where the card's rounded clip cannot cut it off.
*/
function RoleRow({ role }: { role: Role }) {
return (
<li className="relative flex flex-col gap-3 px-4 py-4 transition-colors duration-(--duration-fast) ease-(--ease-standard) hover:bg-muted/50 has-[a:focus-visible]:focus-outline-inset sm:flex-row sm:items-center sm:justify-between sm:gap-6 sm:px-5">
<div className="min-w-0">
<p className="text-sm font-medium">
<Link href={role.href} className="outline-none after:absolute after:inset-0">
{role.title}
</Link>
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{role.team} · {role.location}
</p>
</div>
<div className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
<Avatar size="sm">
<AvatarImage src={avatarFor(role.manager.name)} alt="" />
<AvatarFallback>{getInitials(role.manager.name)}</AvatarFallback>
</Avatar>
<span>{reportsTo(role.manager.name)}</span>
<ArrowRightIcon aria-hidden="true" className="ml-1 hidden size-4 text-foreground sm:block" />
</div>
</li>
)
}
/**
* Hiring, tied to the team: on the left the pitch, the faces of the people a
* new hire would work with — the kit's AvatarGroup, which names each face and
* counts the rest — and what working here comes with; on the right the open
* roles in a framed card, each naming the person it reports to by their face.
* The two columns sit side by side from `lg`.
*
* A server component; every word is in `./team-05.data`.
*/
export function Team05() {
const titleId = React.useId()
const rolesId = React.useId()
const team = TEAM.map((name) => ({ name, src: avatarFor(name) }))
return (
<section
data-section="team-05"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto grid max-w-7xl gap-12 lg:grid-cols-12 lg:gap-16">
<div className="lg:col-span-5">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
<div className="mt-8 flex flex-col gap-3">
<AvatarGroup users={team} max={FACES_SHOWN} size="lg" />
<p className="text-sm text-muted-foreground">{teamCaption(TEAM.length)}</p>
</div>
<ul className="mt-8 flex flex-col gap-3 text-sm text-pretty">
{TERMS.map((term) => (
<li key={term} className="flex items-start gap-2.5">
<CheckIcon aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
{term}
</li>
))}
</ul>
<Link
href={HOW_WE_HIRE.href}
className={cn(buttonVariants({ variant: "link" }), "group/hire mt-8 h-auto px-0")}
>
{HOW_WE_HIRE.label}
<ArrowRightIcon
data-icon="inline-end"
aria-hidden="true"
className="transition-transform duration-(--duration-fast) ease-(--ease-standard) group-hover/hire:translate-x-0.5 motion-reduce:transition-none"
/>
</Link>
</div>
<Card className="lg:col-span-7 lg:self-start">
<CardHeader>
<CardTitle>
<h3 id={rolesId}>{ROLES_TITLE}</h3>
</CardTitle>
<CardAction>
<Badge variant="secondary" className="tabular-nums">
{ROLES.length}
</Badge>
</CardAction>
</CardHeader>
<CardContent className="p-0">
<ul aria-labelledby={rolesId} className="divide-y divide-border">
{ROLES.map((role) => (
<RoleRow key={role.href} role={role} />
))}
</ul>
</CardContent>
</Card>
</div>
</section>
)
}
export default Team05/**
* Every word `team-05` sets: the open roles — each with its team, where it
* can be done from and the person it reports to — beside the team a new
* hire would join and what working here comes with. The people are
* fictional, and each gets the kit's face for their name; replace the roles
* with your own openings and take them down when they close.
*/
export type Role = {
title: string
team: string
/** Where the role can be done from, time zones included. */
location: string
href: string
/** The person the role reports to, who reads every application. */
manager: { name: string; role: string }
}
export const EYEBROW = "Careers"
export const HEADLINE = "Six open roles on a team of 32"
export const LEDE =
"We hire people who write things down and would rather fix a process than work around it. Each role reports to someone on this page, and that person reads every application."
/** Everyone on the team today, the faces a new hire would work with. */
export const TEAM: readonly string[] = [
"Tomasz Wójcik",
"Adaeze Nwosu",
"Leila Haddad",
"Kenji Arakawa",
"Signe Halvorsen",
"Grace O'Connell",
"Ines Moreau",
"Rafael Duarte",
"Priyanka Bose",
"Mateo Rinaldi",
"Hye-jin Park",
"Samuel Asante",
"Freya Lindqvist",
"Arjun Menon",
"Beatriz Costa",
"Oskar Nilsson",
"Tariq Hassan",
"Noor Siddiqui",
"Callum Reid",
"Hana Kobayashi",
"Elliot Mercer",
"Ayo Okafor",
"Astrid Solberg",
"Rui Tavares",
"Lucía Ferreira",
"Daniel Mbeki",
"Chloé Martin",
"Kofi Boateng",
"Maya Goldberg",
"Yusuf Demir",
"Paula Novak",
"Ruth Achebe",
]
/** How many faces the group shows before it counts the rest. */
export const FACES_SHOWN = 6
/** The line under the faces: "The 32 people you would work with". */
export function teamCaption(count: number): string {
return `The ${count} people you would work with`
}
/** What working here comes with, in facts rather than adjectives. */
export const TERMS: readonly string[] = [
"A salary band on every listing, the same in every office",
"A reply to every application within five working days",
"Thirty days of leave, and the office closes between Christmas and New Year",
]
export const HOW_WE_HIRE = { label: "How we hire", href: "/careers/how-we-hire" }
/** The card's title over the list of roles. */
export const ROLES_TITLE = "Open roles"
/** The line naming who a role reports to: "Reports to Tomasz". */
export function reportsTo(name: string): string {
return `Reports to ${name.split(" ")[0]}`
}
export const ROLES: readonly Role[] = [
{
title: "Senior Backend Engineer, Query engine",
team: "Engineering",
location: "Lisbon or remote, UTC−1 to UTC+3",
href: "/careers/senior-backend-engineer-query-engine",
manager: { name: "Tomasz Wójcik", role: "Co-founder and CTO" },
},
{
title: "Frontend Engineer, Reporting",
team: "Engineering",
location: "London or remote, UTC−1 to UTC+3",
href: "/careers/frontend-engineer-reporting",
manager: { name: "Adaeze Nwosu", role: "VP Engineering" },
},
{
title: "Product Designer, Onboarding",
team: "Product and design",
location: "London or Lisbon",
href: "/careers/product-designer-onboarding",
manager: { name: "Leila Haddad", role: "Head of Design" },
},
{
title: "Data Engineer, Connectors",
team: "Data",
location: "Remote, UTC−5 to UTC+3",
href: "/careers/data-engineer-connectors",
manager: { name: "Kenji Arakawa", role: "Head of Data" },
},
{
title: "Support Engineer, Americas hours",
team: "Customer success",
location: "Toronto",
href: "/careers/support-engineer-americas",
manager: { name: "Signe Halvorsen", role: "Head of Customer Success" },
},
{
title: "Revenue Accountant",
team: "Operations",
location: "London",
href: "/careers/revenue-accountant",
manager: { name: "Grace O'Connell", role: "Finance Lead" },
},
]npx shadcn@latest add @vibra/team-05Advisors and investors
team-06The advisors as cards — a face, what they advise on and the role that taught them — and the funds as marks on tiles, each over the round it joined.
import * as React from "react"
import { avatarFor } from "@/lib/avatars"
import { getInitials } from "@/lib/format"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { LogoMark } from "@/components/ui/logo-mark"
import {
ADVISES_ON,
ADVISORS,
ADVISORS_TITLE,
EYEBROW,
HEADLINE,
INVESTORS,
INVESTORS_TITLE,
LEDE,
RAISED,
investorLine,
type Advisor,
} from "./team-06.data"
/**
* One advisor: the face, the name as an h4, what they advise on, and the role
* that taught them — the company's mark names where.
*/
function AdvisorCard({ advisor }: { advisor: Advisor }) {
return (
<li className="flex flex-col panel p-6">
<Avatar className="size-14">
<AvatarImage src={avatarFor(advisor.name)} alt="" />
<AvatarFallback className="text-base">{getInitials(advisor.name)}</AvatarFallback>
</Avatar>
<h4 className="mt-4 text-base font-semibold">{advisor.name}</h4>
<p className="mt-0.5 text-sm text-pretty text-muted-foreground">
{ADVISES_ON} {advisor.focus}
</p>
<p className="mt-auto flex flex-wrap items-center gap-x-2 gap-y-1 pt-5 text-xs text-muted-foreground">
<span>
{advisor.credential.role}
<span className="sr-only">,</span>
</span>
<LogoMark name={advisor.credential.company} size="sm" className="text-foreground" />
</p>
</li>
)
}
/**
* The people and the money behind the company, in two groups under one
* heading: the advisors as cards — a face, a name, what they advise on and
* the role that taught them — and the investors as tiles on the surface
* plane, each fund's mark over the round it joined, with what has been
* raised beside the group's heading. Each group is named by its h3.
*
* A server component; every word and figure is in `./team-06.data`.
*/
export function Team06() {
const titleId = React.useId()
const advisorsId = React.useId()
const investorsId = React.useId()
return (
<section
data-section="team-06"
data-slot="section"
aria-labelledby={titleId}
className="w-full bg-background px-4 py-16 text-foreground md:px-6 md:py-24"
>
<div className="mx-auto max-w-7xl">
<div className="max-w-2xl">
<p className="type-eyebrow">{EYEBROW}</p>
<h2 id={titleId} className="type-display mt-3 text-3xl text-balance md:text-4xl">
{HEADLINE}
</h2>
<p className="mt-4 text-base/7 text-pretty text-muted-foreground">{LEDE}</p>
</div>
<h3 id={advisorsId} className="mt-12 border-b border-rule pb-3 text-base font-semibold md:mt-16">
{ADVISORS_TITLE}
</h3>
<ul aria-labelledby={advisorsId} className="mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-4 lg:gap-6">
{ADVISORS.map((advisor) => (
<AdvisorCard key={advisor.name} advisor={advisor} />
))}
</ul>
<div className="mt-12 flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1 border-b border-rule pb-3 md:mt-16">
<h3 id={investorsId} className="text-base font-semibold">
{INVESTORS_TITLE}
</h3>
<p className="text-sm text-muted-foreground">
<span className="font-medium text-foreground tabular-nums">{RAISED.value}</span> {RAISED.label}
</p>
</div>
<ul aria-labelledby={investorsId} className="mt-6 grid grid-cols-2 gap-4 lg:grid-cols-4 lg:gap-6">
{INVESTORS.map((investor) => (
<li key={investor.company} className="flex flex-col items-center justify-center gap-3 frame px-4 py-8 text-center">
<LogoMark name={investor.company} size="lg" />
<p className="text-xs text-muted-foreground tabular-nums">{investorLine(investor)}</p>
</li>
))}
</ul>
</div>
</section>
)
}
export default Team06/**
* Every word and figure `team-06` sets: the advisors — what each advises on
* and the role that taught them — and the funds that invested, each with the
* round it joined. The advisors are fictional people with the kit's faces for
* their names; the funds and the companies are the kit's fictional marks,
* drawn by `LogoMark`. Replace them with your own backers, in words they
* agreed to.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type Advisor = {
name: string
/** What they advise on, finishing "Advises on …". */
focus: string
/** The role that taught them, and where. */
credential: { role: string; company: LogoName }
}
export type Investor = { company: LogoName; round: string; year: number; led: boolean }
export const EYEBROW = "Backers"
export const HEADLINE = "Advised by people who have run the close we automate"
export const LEDE =
"Four funds have invested in Northwind, and four finance and data leaders advise the team on what to build next and what to leave out."
export const ADVISORS_TITLE = "Advisors"
/** The words before an advisor's focus: "Advises on data infrastructure". */
export const ADVISES_ON = "Advises on"
export const INVESTORS_TITLE = "Investors"
/** What the funds have put in, as the investors' heading reads it. */
export const RAISED = { value: "$18.5M", label: "raised to date" }
/** An investor's line under its mark: "Led the Series A, 2025" or "Seed, 2023". */
export function investorLine(investor: Investor): string {
return `${investor.led ? `Led the ${investor.round}` : investor.round}, ${investor.year}`
}
export const ADVISORS: readonly Advisor[] = [
{
name: "Margaret Oduya",
focus: "revenue recognition and audit",
credential: { role: "Former CFO", company: "Riverside" },
},
{
name: "Sofía Márquez",
focus: "data infrastructure",
credential: { role: "Former VP Data", company: "Wavelength" },
},
{
name: "Rahul Varma",
focus: "selling to finance teams",
credential: { role: "Former COO", company: "Solstice" },
},
{
name: "Eleanor Whitcombe",
focus: "controls and compliance",
credential: { role: "Audit committee chair", company: "Brightwater" },
},
]
export const INVESTORS: readonly Investor[] = [
{ company: "Northgate", round: "Series A", year: 2025, led: true },
{ company: "Ironbark", round: "Seed", year: 2023, led: true },
{ company: "Alder", round: "Series A", year: 2025, led: false },
{ company: "Kestrel", round: "Seed", year: 2023, led: false },
]npx shadcn@latest add @vibra/team-06