Portrait cards that turn to colour on hover and reveal contact buttons (from Marbrook)
import { ArrowUpRight, Mail, Phone } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { Tooltip, TooltipPopup, TooltipTrigger } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import type { Surface } from "../_types/common";
import type { TeamContent, TeamMember } from "../_types/team";
import { defaultTeam5Content } from "./data";
import { SectionHeader, surfaceClass, toneFor } from "./section-header";
function telHref(phone: string): string {
return `tel:${phone.replace(/[^\d+]/g, "")}`;
}
const contactButton =
"flex size-9 items-center justify-center rounded-md bg-white/95 text-primary transition-colors hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white";
function MemberCard({
member,
showBio,
sizes,
}: {
member: TeamMember;
showBio: boolean;
sizes: string;
}) {
return (
<article className="group">
<div className="relative aspect-4/5 overflow-hidden rounded-lg bg-muted">
<Image
alt={member.image.alt}
className="object-cover grayscale transition-all duration-700 ease-out group-hover:scale-105 group-hover:grayscale-0"
fill
sizes={sizes}
src={member.image.src}
/>
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-24 bg-linear-to-t from-primary/80 to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100" />
{/* The contact row is revealed on hover but stays reachable by
keyboard: focus-within keeps it visible while a link holds focus. */}
{(member.phone || member.email) && (
<div className="absolute inset-x-0 bottom-0 flex translate-y-2 items-center gap-3 p-4 opacity-0 transition-all duration-500 group-focus-within:translate-y-0 group-focus-within:opacity-100 group-hover:translate-y-0 group-hover:opacity-100">
{member.phone && (
<Tooltip>
<TooltipTrigger
render={
<a
aria-label={`Call ${member.name}`}
className={contactButton}
href={telHref(member.phone)}
>
<Phone className="size-4" />
</a>
}
/>
<TooltipPopup>{member.phone}</TooltipPopup>
</Tooltip>
)}
{member.email && (
<Tooltip>
<TooltipTrigger
render={
<a
aria-label={`Email ${member.name}`}
className={contactButton}
href={`mailto:${member.email}`}
>
<Mail className="size-4" />
</a>
}
/>
<TooltipPopup>{member.email}</TooltipPopup>
</Tooltip>
)}
</div>
)}
</div>
<h3 className="mt-5 font-display text-2xl text-primary">{member.name}</h3>
<p className="mt-1 text-muted-foreground text-sm">{member.role}</p>
{showBio && member.bio && (
<p className="mt-3 text-muted-foreground text-sm leading-relaxed">
{member.bio}
</p>
)}
{(member.area || member.note) && (
<div className="mt-4 flex items-center justify-between border-border border-t pt-3 text-muted-foreground text-xs">
<span>{member.area}</span>
<span className="text-muted-foreground/70">{member.note}</span>
</div>
)}
</article>
);
}
/** Portrait cards that turn from grey to colour on hover and reveal call and
* email buttons. Four across without bios, three across with them. */
export default function Team5({
columns = 4,
content = defaultTeam5Content,
showBio = false,
surface = "default",
}: {
columns?: 3 | 4;
content?: TeamContent;
showBio?: boolean;
surface?: Surface;
}) {
const sizes =
columns === 3
? "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw"
: "(min-width: 1024px) 25vw, (min-width: 640px) 50vw, 100vw";
return (
<section className={cn("py-20 sm:py-28", surfaceClass[surface])}>
<div className="mx-auto max-w-7xl px-5 sm:px-8 lg:px-12">
<SectionHeader
action={
content.action && (
<Link
className="group inline-flex items-center gap-2 text-primary text-sm underline-offset-4 hover:underline"
href={content.action.href}
>
{content.action.label}
<ArrowUpRight className="size-4 transition-transform duration-300 group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</Link>
)
}
className="border-border border-b pb-8"
description={content.description}
eyebrow={content.eyebrow}
title={content.title}
tone={toneFor(surface)}
/>
<div
className={cn(
"mt-10 grid gap-6 sm:grid-cols-2",
columns === 3 ? "lg:grid-cols-3" : "lg:grid-cols-4",
)}
>
{content.members.map((member) => (
<MemberCard
key={member.name}
member={member}
showBio={showBio}
sizes={sizes}
/>
))}
</div>
</div>
</section>
);
}
