src/registry/blocks/team-section/five/index.tsx
"use client";
import { useState } from "react";
import Image from "next/image";
import { motion, AnimatePresence } from "motion/react";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
interface TeamMember {
id: string;
name: string;
role: string;
image: string;
xUrl?: string;
linkedinUrl?: string;
}
const teamMembers: TeamMember[] = [
{
id: "member-1",
name: "Sarah Chen",
role: "Head of Design",
image: "https://assets.solaceui.com/solaceui-member-five.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-2",
name: "Marcus Rodriguez",
role: "VP of Engineering",
image: "https://assets.solaceui.com/solaceui-member-one.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-3",
name: "James Mitchell",
role: "Product Manager",
image: "https://assets.solaceui.com/solaceui-member-four.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-4",
name: "David Kim",
role: "Founder & CEO",
image: "https://assets.solaceui.com/solaceui-member-two.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-5",
name: "Olivia Koe",
role: "Lead Designer",
image: "https://assets.solaceui.com/solaceui-member-six.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-6",
name: "Amara Okonkwo",
role: "CTO",
image: "https://assets.solaceui.com/solaceui-member-three.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
{
id: "member-7",
name: "Sarah Chen",
role: "Head of Design",
image: "https://assets.solaceui.com/solaceui-member-five.png",
xUrl: "https://x.com",
linkedinUrl: "https://linkedin.com",
},
];
// Layout coordinates normalized to percentages from design coordinates:
// Container aspect ratio: 100/76.1
// Card dimensions: width = 19.65cqw, height = 18.5cqw (Polaroid aspect ratio ~17/16)
const cardLayouts = [
{ left: "58.5cqw", top: "6cqw" }, // Card 1: Sarah Chen
{ left: "80.35cqw", top: "6cqw" }, // Card 2: Marcus Rodriguez
{ left: "0cqw", top: "31.8cqw" }, // Card 3: James Mitchell
{ left: "21.85cqw", top: "31.8cqw" }, // Card 4: David Kim
{ left: "80.35cqw", top: "31.8cqw" }, // Card 5: Olivia Koe
{ left: "58.5cqw", top: "57.6cqw" }, // Card 6: Amara Okonkwo
{ left: "80.35cqw", top: "57.6cqw" }, // Card 7: Sarah Chen (repeated)
];
const TwitterIcon = () => (
<svg
width="20"
height="18"
viewBox="0 0 25 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="w-5 h-auto text-white/50 hover:text-white transition-colors"
>
<path
d="M10.558 14.078L16.313 21.75H24.771L15.276 9.089L23.176 0H19.974L13.791 7.112L8.458 0H0L9.075 12.101L0.689 21.75H3.891L10.558 14.078ZM17.521 19.333L4.833 2.417H7.25L19.938 19.333H17.521Z"
fill="currentColor"
/>
</svg>
);
const LinkedInIcon = () => (
<svg
width="18"
height="18"
viewBox="0 0 23 23"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="w-4.5 h-auto text-white/50 hover:text-white transition-colors"
>
<path
d="M20 0C20.663 0 21.299 0.263 21.768 0.732C22.237 1.201 22.5 1.837 22.5 2.5V20C22.5 20.663 22.237 21.299 21.768 21.768C21.299 22.237 20.663 22.5 20 22.5H2.5C1.837 22.5 1.201 22.237 0.732 21.768C0.263 21.299 0 20.663 0 20V2.5C0 1.837 0.263 1.201 0.732 0.732C1.201 0.263 1.837 0 2.5 0H20ZM19.375 19.375V12.75C19.375 11.669 18.946 10.633 18.181 9.869C17.417 9.104 16.381 8.675 15.3 8.675C14.238 8.675 13 9.325 12.4 10.3V8.912H8.912V19.375H12.4V13.213C12.4 12.25 13.175 11.463 14.137 11.463C14.602 11.463 15.047 11.647 15.375 11.975C15.703 12.303 15.887 12.748 15.887 13.213V19.375H19.375ZM4.85 6.95C5.407 6.95 5.941 6.729 6.335 6.335C6.729 5.941 6.95 5.407 6.95 4.85C6.95 3.688 6.013 2.737 4.85 2.737C4.29 2.737 3.752 2.96 3.356 3.356C2.96 3.752 2.737 4.29 2.737 4.85C2.737 6.013 3.688 6.95 4.85 6.95ZM6.588 19.375V8.912H3.125V19.375H6.588Z"
fill="currentColor"
/>
</svg>
);
export default function Team5() {
const [selectedMember, setSelectedMember] = useState<TeamMember | null>(null);
return (
<section className="relative w-full py-20 md:py-28 bg-white dark:bg-background overflow-hidden flex flex-col items-center justify-center">
{/* Dashed background pattern spanning end-to-end (full viewport width) */}
<div className="absolute inset-x-0 top-1/2 -translate-y-1/2 h-[82%] z-0 opacity-10 bg-[repeating-linear-gradient(315deg,currentColor_0,currentColor_1px,transparent_0,transparent_50%)] bg-[length:10px_10px] border-y border-black dark:border-white pointer-events-none"></div>
<div className="w-full max-w-5xl px-4 md:px-8 z-10 flex flex-col items-center justify-center">
{/* Main Grid Wrapper utilizing Container Queries for flawless scaling */}
<div
className={cn(
"@container relative w-full aspect-[100/76.1] z-10 transition-all duration-500 ease-in-out",
selectedMember ? "opacity-15 pointer-events-none blur-[1.5px]" : "opacity-100"
)}
>
{/* Background Text "Meet" - Styled to sit at same top and height as Row 1 Cards */}
<div
className="absolute left-0 font-sans font-semibold text-black dark:text-white select-none tracking-tight flex items-center leading-none"
style={{
top: "6cqw",
height: "18.5cqw",
fontSize: "25cqw",
}}
>
Meet
</div>
{/* Background Text "The" - Styled to sit at same top and height as Row 2 Cards */}
<div
className="absolute font-sans font-semibold text-black dark:text-white select-none tracking-tight flex items-center leading-none"
style={{
left: "43.7cqw",
top: "31.8cqw",
height: "18.5cqw",
fontSize: "25cqw",
}}
>
The
</div>
{/* Background Text "Team" - Styled to sit at same top and height as Row 3 Cards */}
<div
className="absolute left-0 font-sans font-semibold text-black dark:text-white select-none tracking-tight flex items-center leading-none"
style={{
top: "57.6cqw",
height: "18.5cqw",
fontSize: "25cqw",
}}
>
Team
</div>
{/* Polaroid Cards - Positioned on the exact same levels and heights as the text boxes */}
{teamMembers.map((member, index) => {
const layout = cardLayouts[index];
return (
<motion.div
key={`${member.id}-${index}`}
layoutId={`card-${member.id}-${index}`}
onClick={() => setSelectedMember(member)}
className="absolute p-[1%] bg-white/95 dark:bg-zinc-900/95 border border-neutral-200/50 dark:border-zinc-800/50 shadow-md shadow-black/5 hover:shadow-xl dark:shadow-black/30 rounded-[4%] cursor-pointer flex flex-col justify-between"
style={{
left: layout.left,
top: layout.top,
width: "19.65cqw",
height: "18.5cqw",
}}
whileHover={{
scale: 1.04,
rotate: index % 2 === 0 ? 1 : -1,
}}
transition={{
type: "spring",
stiffness: 300,
damping: 20,
}}
>
<div className="relative w-full h-[85%] rounded-[3%] overflow-hidden bg-neutral-100 dark:bg-zinc-800">
<Image
src={member.image}
alt={member.name}
fill
className={cn(
"object-cover object-top transition-all duration-500",
"grayscale hover:grayscale-0"
)}
sizes="(max-width: 768px) 15vw, 10vw"
unoptimized
/>
</div>
{/* Subtle placeholder line to balance the Polaroid look */}
<div className="w-1/3 h-1 bg-neutral-200 dark:bg-zinc-700 rounded-full mx-auto mt-auto opacity-40" />
</motion.div>
);
})}
</div>
</div>
{/* Expanded Modal Card */}
<AnimatePresence>
{selectedMember && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setSelectedMember(null)}
className="absolute inset-0 bg-white/20 dark:bg-black/40 backdrop-blur-md cursor-pointer"
/>
{/* Modal Detail Card */}
{(() => {
// Find the index of the selected member to get its original slot for layout animation
const index = teamMembers.findIndex((m) => m.id === selectedMember.id);
return (
<motion.div
layoutId={`card-${selectedMember.id}-${index}`}
className="relative w-full max-w-[340px] sm:max-w-[400px] aspect-[134/193.75] bg-black text-white p-3.5 pb-6 rounded-xl border-4 border-black shadow-2xl z-50 flex flex-col justify-between"
transition={{
type: "spring",
stiffness: 260,
damping: 25,
}}
>
{/* Close button on top-right of image */}
<button
onClick={() => setSelectedMember(null)}
className="absolute top-6 right-6 z-50 p-1.5 rounded-full bg-black/60 hover:bg-black/90 text-white/80 hover:text-white border border-white/10 transition-colors cursor-pointer"
aria-label="Close profile details"
>
<X className="w-4 h-4" />
</button>
{/* Polaroid Image Frame - Styled to be larger with thin padding around the edges */}
<div className="relative w-full h-[78%] rounded-lg overflow-hidden bg-zinc-900 border border-zinc-800/55">
<Image
src={selectedMember.image}
alt={selectedMember.name}
fill
className="object-cover object-top"
sizes="(max-width: 768px) 80vw, 40vw"
priority
unoptimized
/>
</div>
{/* Member Info & Socials Section */}
<div className="mt-4 flex items-end justify-between px-2">
<div className="flex flex-col text-left">
<h3 className="font-sans font-semibold text-white text-xl sm:text-2xl tracking-tight leading-none">
{selectedMember.name}
</h3>
<p className="font-sans font-light text-neutral-400 text-sm sm:text-base mt-2.5 leading-none">
{selectedMember.role}
</p>
</div>
<div className="flex items-center gap-3 pb-0.5">
{selectedMember.xUrl && (
<a
href={selectedMember.xUrl}
target="_blank"
rel="noopener noreferrer"
className="hover:text-white transition-colors"
aria-label="Twitter profile"
>
<TwitterIcon />
</a>
)}
{selectedMember.linkedinUrl && (
<a
href={selectedMember.linkedinUrl}
target="_blank"
rel="noopener noreferrer"
className="hover:text-white transition-colors"
aria-label="LinkedIn profile"
>
<LinkedInIcon />
</a>
)}
</div>
</div>
</motion.div>
);
})()}
</div>
)}
</AnimatePresence>
</section>
);
}






























