Skip to content
Open
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
153 changes: 70 additions & 83 deletions app/people/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { PeopleStats } from "@/components/people/PeopleStats";
import { PeopleGrid } from "@/components/people/PeopleGrid";
import { ContributorDetail } from "@/components/people/ContributorDetail";
import { TeamSection } from "@/components/people/TeamSection";
import { PeopleHero } from "@/components/people/PeopleHero";
import { type TeamMember } from "@/lib/team-data";
import { Input } from "@/components/ui/input";
import { Search } from "lucide-react";
Expand Down Expand Up @@ -57,7 +58,6 @@ export default function PeoplePage() {
const [people, setPeople] = useState<ContributorEntry[]>([]);
const [coreTeam, setCoreTeam] = useState<TeamMember[]>([]);
const [alumni, setAlumni] = useState<TeamMember[]>([]);
const [updatedAt, setUpdatedAt] = useState<number | null>(null);
const [selectedContributor, setSelectedContributor] = useState<ContributorEntry | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
Expand Down Expand Up @@ -90,7 +90,6 @@ export default function PeoplePage() {
setPeople(data.people);
setCoreTeam(data.coreTeam || []);
setAlumni(data.alumni || []);
setUpdatedAt(data.updatedAt);
} catch (error) {
console.error('Failed to load contributors:', error);
setError('Failed to load contributors. Please try again.');
Expand All @@ -102,17 +101,17 @@ export default function PeoplePage() {
loadData();
}, []);

const filteredPeople = useMemo(() => {
if (!searchQuery.trim()) return people;
const filteredPeople = useMemo(() => {
if (!searchQuery.trim()) return people;

const query = searchQuery.toLowerCase();
const query = searchQuery.toLowerCase();

return people.filter((person) => {
const name = person.name?.toLowerCase() || "";
const username = person.username.toLowerCase();
return name.includes(query) || username.includes(query);
});
}, [people, searchQuery]);
return people.filter((person) => {
const name = person.name?.toLowerCase() || "";
const username = person.username.toLowerCase();
return name.includes(query) || username.includes(query);
});
}, [people, searchQuery]);


const handleContributorClick = (contributor: ContributorEntry) => {
Expand All @@ -123,9 +122,9 @@ const filteredPeople = useMemo(() => {

if (selectedContributor) {
return (
<ContributorDetail
contributor={selectedContributor}
onBack={() => setSelectedContributor(null)}
<ContributorDetail
contributor={selectedContributor}
onBack={() => setSelectedContributor(null)}
/>
);
}
Expand All @@ -136,8 +135,8 @@ const filteredPeople = useMemo(() => {
<div className="p-8 bg-destructive/5 border border-destructive/20 rounded-lg">
<h2 className="text-xl font-semibold text-destructive mb-2">Something went wrong</h2>
<p className="text-muted-foreground mb-4">{error}</p>
<Button
onClick={() => window.location.reload()}
<Button
onClick={() => window.location.reload()}
variant="outline"
className="hover:bg-destructive hover:text-destructive-foreground"
>
Expand All @@ -148,23 +147,11 @@ const filteredPeople = useMemo(() => {
);
}

const totalPeopleCount = coreTeam.length + alumni.length + people.length;

return (
<div className="mx-auto px-4 py-8 max-w-7xl">
<div className="mb-8 text-center">
<h1 className="text-4xl font-bold">
<span className="text-black dark:text-white">Our </span>
<span className="text-emerald-600 dark:text-emerald-400">People</span>
</h1>
<p className="text-lg text-muted-foreground max-w-2xl mx-auto mb-4 mt-4">
Meet the team who made CircuitVerse possible.
</p>
{updatedAt && (
<div className="flex items-center justify-center gap-2 text-sm text-muted-foreground">
<Activity className="w-4 h-4" />
<span>Updated {new Date(updatedAt).toLocaleString()}</span>
</div>
)}
</div>
<PeopleHero coreTeam={coreTeam} totalCount={totalPeopleCount} />

{loading ? (
<div className="space-y-16">
Expand Down Expand Up @@ -274,62 +261,62 @@ const filteredPeople = useMemo(() => {
members={alumni}
teamType="alumni"
/>
<section id="contributors" className="mb-8 scroll-mt-28">
<div className="mb-8">
<div className="mb-4">
<h2 className="text-3xl font-bold">
<span className="text-black dark:text-white">Community </span>
<span className="text-[#42B883]">Contributors</span>
</h2>
</div>
<section id="contributors" className="mb-8 scroll-mt-28">
<div className="mb-8 text-center">
<div className="mb-4">
<h2 className="text-3xl font-bold">
<span className="text-black dark:text-white">Community </span>
<span className="text-[#42B883]">Contributors</span>
</h2>
</div>

<p className="text-lg text-muted-foreground max-w-3xl mb-6">
Amazing community members who contribute to CircuitVerse through
code, documentation, and more.
</p>
</div>

<div className="flex flex-col gap-4">
<PeopleStats
contributors={filteredPeople}
allContributors={people}
onContributorClick={handleContributorClick}
/>

<div className="flex items-center justify-between gap-4 py-8">
<div className="flex items-center gap-2">
<Users className="w-5 h-5 text-muted-foreground" />
<span className="text-2xl font-bold text-foreground">
{filteredPeople.length}{' '}
<span className="text-[#42B883]">
{filteredPeople.length === 1 ? 'Contributor' : 'Contributors'}
</span>
{searchQuery && <span className="text-foreground"> found</span>}
</span>
</div>
<p className="text-lg text-muted-foreground max-w-3xl mb-6 mx-auto">
Amazing community members who contribute to CircuitVerse through
code, documentation, and more.
</p>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>

<div className="relative w-full sm:w-72">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search contributors..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-10"
/>
</div>
</div>
<div className="flex flex-col gap-4">
<PeopleStats
contributors={filteredPeople}
allContributors={people}
onContributorClick={handleContributorClick}
/>

<div className="flex items-center justify-between gap-4 py-8">
<div className="flex items-center gap-2">
<Users className="w-5 h-5 text-muted-foreground" />
<span className="text-2xl font-bold text-foreground">
{filteredPeople.length}{' '}
<span className="text-[#42B883]">
{filteredPeople.length === 1 ? 'Contributor' : 'Contributors'}
</span>
{searchQuery && <span className="text-foreground"> found</span>}
</span>
</div>

<div className="relative w-full sm:w-72">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="text"
placeholder="Search contributors..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9 h-10"
/>
</div>
</div>

<PeopleGrid
contributors={filteredPeople}
onContributorClick={handleContributorClick}
viewMode="grid"
loading={false}
/>
</div>
</section>

<PeopleGrid
contributors={filteredPeople}
onContributorClick={handleContributorClick}
viewMode="grid"
loading={false}
/>
</div>
</section>


</>
)}
</div>
Expand Down
62 changes: 62 additions & 0 deletions components/people/PeopleHero.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";

import Image from "next/image";
import { type TeamMember } from "@/lib/team-data";

interface PeopleHeroProps {
coreTeam?: TeamMember[];
totalCount?: number;
}

export function PeopleHero({ coreTeam = [], totalCount = 0 }: PeopleHeroProps) {
// Display top 8 core team members as avatars for the hero
const displayTeam = coreTeam.slice(0, 8);

return (
<div className="relative mb-20 overflow-hidden rounded-3xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 px-6 py-16 md:px-12 md:py-24">
{/* Subtle Background Accent */}
<div className="absolute top-0 right-0 -mr-24 -mt-24 h-96 w-96 rounded-full bg-emerald-500/5 blur-[100px]" />
<div className="absolute bottom-0 left-0 -ml-24 -mb-24 h-96 w-96 rounded-full bg-blue-500/5 blur-[100px]" />

<div className="relative z-10 flex flex-col items-center text-center">
<h1 className="text-5xl md:text-7xl font-black tracking-tight mb-6">
<span className="text-zinc-900 dark:text-zinc-100 italic">Our </span>
<span className="bg-gradient-to-r from-emerald-500 to-blue-500 bg-clip-text text-transparent">
People
</span>
</h1>

<p className="max-w-2xl text-lg md:text-xl text-zinc-600 dark:text-zinc-400 font-medium leading-relaxed mb-10">
Meet the visionary team and the vibrant community driving CircuitVerse towards a better future for digital logic education.
</p>

{/* Core Team Avatars Stack - Moved to bottom */}
{displayTeam.length > 0 && (
<div className="flex -space-x-3">
{displayTeam.map((member, index) => (
<div
key={member.username}
style={{ zIndex: index + 1 }}
className="relative flex-shrink-0 h-12 w-12 rounded-full ring-2 ring-white dark:ring-zinc-950 grayscale hover:grayscale-0 transition-all duration-300 transform hover:scale-110 hover:z-50"
>
<Image
src={member.avatar_url}
alt={member.name}
width={48}
height={48}
className="rounded-full object-cover"
/>
</div>
))}
<div
style={{ zIndex: displayTeam.length + 1 }}
className="relative flex-shrink-0 flex h-12 w-12 items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800 ring-2 ring-white dark:ring-zinc-950 text-xs font-bold text-zinc-900 dark:text-zinc-100 shadow-sm"
>
{totalCount}+
</div>
</div>
)}
</div>
</div>
);
}
31 changes: 17 additions & 14 deletions components/people/TeamSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@ interface TeamSectionProps {
}

export function TeamSection({ title, description, members, teamType }: TeamSectionProps) {
const resolvedColorClass =
teamType === "core"
? "text-blue-600 dark:text-blue-400"
: "text-orange-600 dark:text-orange-400";
const isCore = teamType === "core";
const resolvedColorClass =
teamType === "core"
? "text-blue-600 dark:text-blue-400"
: "text-orange-600 dark:text-orange-400";
return (
<div className="mb-16">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold mb-4">
<span className="text-black dark:text-white">Our </span>
<div className="mb-20">
<div className="flex flex-col items-center text-center mb-12 relative">
<h2 className="text-4xl font-black tracking-tight mb-4">
<span className="text-zinc-900 dark:text-zinc-100">{isCore ? 'The ' : 'Our '}</span>
<span className={resolvedColorClass}>{title}</span>
</h2>
<p className="text-lg text-muted-foreground max-w-3xl mx-auto">

<p className="text-lg text-muted-foreground max-w-3xl leading-relaxed mx-auto">
{description}
</p>

<div className={`w-24 h-1 bg-gradient-to-r from-transparent ${isCore ? 'via-blue-500/50' : 'via-orange-500/50'} to-transparent mt-6 rounded-full`} />
</div>

<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-6">
Expand Down Expand Up @@ -65,11 +69,10 @@ const resolvedColorClass =
</p>

<div
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
teamType === "core"
? "bg-[#42B883]/10 text-[#42B883] border border-[#42B883]/20"
: "bg-[#FF6B35]/10 text-[#FF6B35] border border-[#FF6B35]/20"
}`}
className={`inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${teamType === "core"
? "bg-[#42B883]/10 text-[#42B883] border border-[#42B883]/20"
: "bg-[#FF6B35]/10 text-[#FF6B35] border border-[#FF6B35]/20"
}`}
>
{member.role}
</div>
Expand Down
1 change: 1 addition & 0 deletions components/people/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { PeopleGrid } from './PeopleGrid';
export { PeopleStats } from './PeopleStats';
export { SearchFilter } from './SearchFilter';
export { TeamSection } from './TeamSection';
export { PeopleHero } from './PeopleHero';
export type { FilterState } from './SearchFilter';