All checks were successful
Build pitch-deck / build-push-deploy (push) Successful in 1m11s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 33s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 31s
- grid items-stretch so cards match height - Smaller avatar (16->64px) to free vertical space - Equity moved to a top-right pill (compact); decimals collapsed via equityDisplay() - Profile link icon auto-detects GitHub vs LinkedIn vs generic - Expertise tags get their own divider strip at card bottom — cleaner hierarchy - Card background lightened from 0.08 to 0.04 with subtle hover border Bio text itself shortened on the data side (both draft versions via admin API).
123 lines
4.8 KiB
TypeScript
123 lines
4.8 KiB
TypeScript
'use client'
|
|
|
|
import { motion } from 'framer-motion'
|
|
import { Language, PitchTeamMember } from '@/lib/types'
|
|
import { t } from '@/lib/i18n'
|
|
import { User, Linkedin, Github } from 'lucide-react'
|
|
import GradientText from '../ui/GradientText'
|
|
import FadeInView from '../ui/FadeInView'
|
|
import Image from 'next/image'
|
|
|
|
interface TeamSlideProps {
|
|
lang: Language
|
|
team: PitchTeamMember[]
|
|
}
|
|
|
|
function equityDisplay(pct: number | string | null | undefined): string {
|
|
const n = Number(pct)
|
|
if (!Number.isFinite(n)) return '—'
|
|
return Number.isInteger(n) ? `${n}%` : `${n.toFixed(1)}%`
|
|
}
|
|
|
|
function detectProfileLink(url: string | null | undefined): { icon: typeof Linkedin | typeof Github; label: string } | null {
|
|
if (!url) return null
|
|
if (url.includes('github.com')) return { icon: Github, label: 'GitHub' }
|
|
if (url.includes('linkedin.com')) return { icon: Linkedin, label: 'LinkedIn' }
|
|
return { icon: Linkedin, label: 'Profile' }
|
|
}
|
|
|
|
export default function TeamSlide({ lang, team }: TeamSlideProps) {
|
|
const i = t(lang)
|
|
|
|
return (
|
|
<div>
|
|
<FadeInView className="text-center mb-8">
|
|
<h2 className="text-4xl md:text-5xl font-bold mb-3">
|
|
<GradientText>{i.team.title}</GradientText>
|
|
</h2>
|
|
<p className="text-lg text-white/50 max-w-2xl mx-auto">{i.team.subtitle}</p>
|
|
</FadeInView>
|
|
|
|
<div className="grid md:grid-cols-2 gap-6 max-w-5xl mx-auto items-stretch">
|
|
{team.map((member, idx) => {
|
|
const link = detectProfileLink(member.linkedin_url)
|
|
const LinkIcon = link?.icon
|
|
return (
|
|
<motion.div
|
|
key={member.id}
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.2 + idx * 0.15, duration: 0.5 }}
|
|
className="bg-white/[0.04] backdrop-blur-xl border border-white/[0.08] rounded-2xl p-6 flex flex-col hover:border-indigo-500/20 transition-colors"
|
|
>
|
|
{/* Header: avatar + name + role */}
|
|
<div className="flex items-center gap-4 mb-5">
|
|
{member.photo_url ? (
|
|
<div className="w-16 h-16 rounded-2xl overflow-hidden shrink-0 shadow-lg">
|
|
<Image
|
|
src={member.photo_url}
|
|
alt={member.name}
|
|
width={64}
|
|
height={64}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
) : (
|
|
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center shrink-0 shadow-lg shadow-indigo-500/20">
|
|
<User className="w-8 h-8 text-white" />
|
|
</div>
|
|
)}
|
|
|
|
<div className="min-w-0 flex-1">
|
|
<div className="flex items-center gap-2 mb-0.5 flex-wrap">
|
|
<h3 className="text-xl font-bold text-white truncate">{member.name}</h3>
|
|
{link && LinkIcon && (
|
|
<a
|
|
href={member.linkedin_url!}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-white/30 hover:text-indigo-300 transition-colors"
|
|
title={link.label}
|
|
>
|
|
<LinkIcon className="w-4 h-4" />
|
|
</a>
|
|
)}
|
|
</div>
|
|
<p className="text-indigo-400 text-sm font-medium">
|
|
{lang === 'de' ? member.role_de : member.role_en}
|
|
</p>
|
|
</div>
|
|
|
|
{/* Equity pill in top-right */}
|
|
<div className="text-right shrink-0">
|
|
<div className="text-[10px] uppercase tracking-wider text-white/30">{i.team.equity}</div>
|
|
<div className="text-base font-bold text-white tabular-nums">{equityDisplay(member.equity_pct)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Bio */}
|
|
<p className="text-sm text-white/60 leading-relaxed mb-5 flex-1">
|
|
{lang === 'de' ? member.bio_de : member.bio_en}
|
|
</p>
|
|
|
|
{/* Expertise tags */}
|
|
{(member.expertise || []).length > 0 && (
|
|
<div className="flex flex-wrap gap-1.5 pt-4 border-t border-white/[0.06]">
|
|
{(member.expertise || []).map((skill, sidx) => (
|
|
<span
|
|
key={sidx}
|
|
className="text-[11px] px-2.5 py-1 rounded-full bg-indigo-500/10 text-indigo-300 border border-indigo-500/20"
|
|
>
|
|
{skill}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</motion.div>
|
|
)
|
|
})}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|