Redesign homepage: editorial layout with current focus + featured work

Replaced the dense mini-CV homepage with an editorial landing page:

- Hero: punchy one-liner ("Researcher & builder. HRI at BU, motorsports
  software at Riverhead.") instead of a paragraph bio
- Currently building: Riverhead Raceway mobile app (racehub-app) and
  Hotlap (Go monorepo race event management platform)
- Featured work: Honors thesis, IEEE RO-MAN 2025, Riverhead Raceway site,
  beenvoice — each with description, tags, and a direct action button
- Education: compact 2-col grid (BU + Bucknell) replaces the full card layout
- Explore: same 3-col nav to Projects / Publications / CV

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 01:47:14 -04:00
parent 98196748ae
commit 94a1592dff
+366 -356
View File
@@ -1,16 +1,15 @@
import { import {
ArrowUpRight, ArrowUpRight,
Award,
BookOpen, BookOpen,
Building,
Code, Code,
ExternalLink, ExternalLink,
FlaskConical, FileText,
Flag,
Globe,
GraduationCap, GraduationCap,
Mail, Mail,
MapPin, MapPin,
School, Smartphone,
Users
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { Badge } from "~/components/ui/badge"; import { Badge } from "~/components/ui/badge";
@@ -22,60 +21,56 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { awards, educationList, experiences, researchInterests } from "~/lib/data"; import { educationList } from "~/lib/data";
function SectionLabel({ children }: { children: React.ReactNode }) {
return (
<h2 className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
{children}
</h2>
);
}
export default function HomePage() { export default function HomePage() {
const researchExperience = experiences.filter(
(exp) => exp.type === "research",
);
const teachingExperience = experiences.filter(
(exp) => exp.type === "teaching",
);
const professionalExperience = experiences.filter(
(exp) => exp.type === "professional",
);
const leadershipExperience = experiences.filter(
(exp) => exp.type === "leadership",
);
return ( return (
<div className="space-y-12"> <div className="space-y-16">
{/* Hero Section */}
<section className="animate-fade-in-up space-y-6"> {/* Hero */}
<section className="animate-fade-in-up space-y-5 pt-2">
<div className="space-y-4"> <div className="space-y-4">
<h1 className="animate-fade-in-up-delay-1 text-3xl font-bold"> <h1 className="animate-fade-in-up-delay-1 text-4xl font-bold tracking-tight">
Sean O&apos;Connor Sean O&apos;Connor
</h1> </h1>
<p className="animate-fade-in-up-delay-2 text-xl text-muted-foreground"> <p className="animate-fade-in-up-delay-2 max-w-lg text-lg text-muted-foreground">
Computer Science and Engineering graduate pursuing a Master&apos;s Researcher & builder. HRI at BU, motorsports software at Riverhead.
at Boston University. Research interests in human-robot interaction
and developing technologies that make robots better collaborators
with humans.
</p> </p>
<div className="animate-fade-in-up-delay-3 flex flex-wrap gap-4 text-sm text-muted-foreground"> <div className="animate-fade-in-up-delay-3 flex flex-wrap gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1.5">
<Mail className="h-4 w-4" /> <Mail className="h-4 w-4" />
<a href="mailto:sean@soconnor.dev" className="hover:text-primary"> <a
href="mailto:sean@soconnor.dev"
className="transition-colors hover:text-foreground"
>
sean@soconnor.dev sean@soconnor.dev
</a> </a>
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1.5">
<GraduationCap className="h-4 w-4" /> <GraduationCap className="h-4 w-4" />
Boston University Boston University
</div> </div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1.5">
<MapPin className="h-4 w-4" /> <MapPin className="h-4 w-4" />
Boston, MA Boston, MA
</div> </div>
</div> </div>
<div className="animate-fade-in-up-delay-4 flex gap-3"> <div className="animate-fade-in-up-delay-4 flex gap-3">
<Button variant="outline" asChild className="button-hover"> <Button variant="outline" asChild>
<Link href="/cv"> <Link href="/cv">
<ExternalLink className="mr-2 h-4 w-4" /> <ExternalLink className="mr-2 h-4 w-4" />
View CV CV
</Link> </Link>
</Button> </Button>
<Button variant="outline" asChild className="button-hover"> <Button variant="outline" asChild>
<Link href="/publications"> <Link href="/publications">
<BookOpen className="mr-2 h-4 w-4" /> <BookOpen className="mr-2 h-4 w-4" />
Publications Publications
@@ -85,343 +80,358 @@ export default function HomePage() {
</div> </div>
</section> </section>
{/* Research Interests */} {/* Currently Building */}
<section className="animate-fade-in-up space-y-6"> <section className="animate-fade-in-up space-y-4">
<h2 className="text-2xl font-bold">Research Interests</h2> <SectionLabel>Currently building</SectionLabel>
<div className="animate-fade-in-up-delay-1"> <div className="grid gap-4 md:grid-cols-2">
<Card className="card-hover">
<CardContent> <Card className="card-hover card-full-height">
<p className="leading-relaxed text-muted-foreground"> <CardHeader>
{researchInterests} <div className="flex items-start gap-3">
</p> <div className="rounded-xl bg-primary/10 p-2.5">
<Smartphone className="h-5 w-5 text-primary" />
</div>
<div className="min-w-0">
<CardTitle>Riverhead Raceway Mobile App</CardTitle>
<CardDescription>racehub-app · React Native</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Native mobile companion for Riverhead Raceway race-day
results, live standings, and event info for fans and
competitors on the track.
</p>
<div className="flex flex-wrap gap-1.5">
{["React Native", "TypeScript", "tRPC"].map((t) => (
<Badge key={t} variant="secondary">
{t}
</Badge>
))}
</div>
</div>
</CardContent> </CardContent>
</Card> </Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start gap-3">
<div className="rounded-xl bg-primary/10 p-2.5">
<Flag className="h-5 w-5 text-primary" />
</div>
<div className="min-w-0">
<CardTitle>Hotlap</CardTitle>
<CardDescription>
Race event management · Go monorepo
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Full-stack race event management platform in Go.
Server-rendered public pages, a Solid.js SPA for track ops,
and a custom Go-native bundler powering the whole thing.
</p>
<div className="flex flex-wrap gap-1.5">
{["Go", "Solid.js", "PostgreSQL", "TypeScript"].map((t) => (
<Badge key={t} variant="secondary">
{t}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
</div>
</section>
{/* Featured Work */}
<section className="animate-fade-in-up space-y-4">
<SectionLabel>Featured work</SectionLabel>
<div className="grid-equal-height grid gap-4 md:grid-cols-2">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>Honors Thesis</CardTitle>
<CardDescription>
Bucknell University · 2026
</CardDescription>
</div>
<FileText className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
A web-based Wizard-of-Oz platform for accessible, reproducible
HRI research. Pilot study showed higher design fidelity and
usability than Choregraphe across all six sessions.
</p>
<div className="flex flex-wrap gap-1.5">
{["HRI", "Wizard-of-Oz", "React", "ROS2"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<Link href="/publications">
Read thesis
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>IEEE RO-MAN 2025</CardTitle>
<CardDescription>
Eindhoven · First author
</CardDescription>
</div>
<BookOpen className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Collaborative and Reproducible HRI Research Through a
Web-Based Wizard-of-Oz Platform. 34th IEEE International
Conference on Robot and Human Interactive Communication.
</p>
<div className="flex flex-wrap gap-1.5">
{["IEEE", "HRI", "Research"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<Link href="/publications">
View paper
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>Riverhead Raceway</CardTitle>
<CardDescription>
Production · 250k+ monthly users
</CardDescription>
</div>
<Globe className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Official website and CMS for Riverhead Raceway, NY.
Real-time race data, event management, standings, and a full
admin interface with 97+ granular permissions.
</p>
<div className="flex flex-wrap gap-1.5">
{["Next.js", "PostgreSQL", "tRPC"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<a
href="https://riverheadraceway.com"
target="_blank"
rel="noopener noreferrer"
>
Visit site
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</a>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle>beenvoice</CardTitle>
<CardDescription>Invoicing for freelancers</CardDescription>
</div>
<Code className="h-5 w-5 flex-shrink-0 text-muted-foreground" />
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Professional invoicing: client management, PDF generation,
email delivery, timesheet view, and CSV import. Built with
tRPC and Drizzle ORM on Next.js 16.
</p>
<div className="flex flex-wrap gap-1.5">
{["Next.js", "tRPC", "Drizzle"].map((t) => (
<Badge key={t} variant="outline">
{t}
</Badge>
))}
</div>
<Button
variant="outline"
size="sm"
asChild
className="mt-auto w-full"
>
<a
href="https://beenvoice.soconnor.dev"
target="_blank"
rel="noopener noreferrer"
>
Try it
<ArrowUpRight className="ml-1.5 h-3.5 w-3.5" />
</a>
</Button>
</div>
</CardContent>
</Card>
</div> </div>
</section> </section>
{/* Education */} {/* Education */}
<section className="animate-fade-in-up space-y-6"> <section className="animate-fade-in-up space-y-4">
<h2 className="text-2xl font-bold">Education</h2> <SectionLabel>Education</SectionLabel>
<div className="space-y-4"> <div className="grid gap-4 md:grid-cols-2">
{educationList.map((edu, index) => ( {educationList.map((edu, i) => (
<div key={index} className={`animate-fade-in-up-delay-${index + 1}`}> <Card key={i} className="card-hover">
<Card className="card-hover"> <CardHeader>
<CardHeader> <CardTitle>{edu.institution}</CardTitle>
<div className="flex items-center justify-between"> <CardDescription>{edu.degree}</CardDescription>
<div> </CardHeader>
<CardTitle className="mb-1">{edu.institution}</CardTitle> <CardContent>
<CardDescription>{edu.location}</CardDescription> <p className="text-sm text-muted-foreground">
</div> {edu.graduated ? "" : "Expected "}
<School className="h-5 w-5 text-muted-foreground" /> {edu.expectedGraduation} · {edu.location}
</p>
{edu.engineeringGpa && (
<div className="mt-2 flex flex-wrap gap-1.5">
<Badge variant="secondary">
Eng. GPA {edu.engineeringGpa}
</Badge>
<Badge variant="outline">Overall {edu.gpa}</Badge>
</div> </div>
</CardHeader>
<CardContent className="space-y-3">
<div>
<p className="font-medium">{edu.degree}</p>
<p className="text-sm text-muted-foreground">
{edu.graduated ? "" : "Expected "}{edu.expectedGraduation}
</p>
</div>
<div className="flex flex-wrap gap-2">
{edu.engineeringGpa ? (
<>
<Badge variant="secondary">Eng. GPA: {edu.engineeringGpa}</Badge>
<Badge variant="outline">Overall GPA: {edu.gpa}</Badge>
</>
) : (
<Badge variant="secondary">GPA: {edu.gpa}</Badge>
)}
{edu.deansListSemesters.length > 0 && (
<Badge variant="outline">
Dean&apos;s List: {edu.deansListSemesters.length} semesters
</Badge>
)}
</div>
</CardContent>
</Card>
</div>
))}
</div>
</section>
{/* Research Experience */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Research Experience</h2>
<div className="space-y-6">
{researchExperience.map((exp, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${index + 1}`}
>
<Card className="card-hover">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="mb-1">{exp.title}</CardTitle>
<CardDescription>{exp.organization}</CardDescription>
<CardDescription className="text-xs">
{exp.location} {exp.period}
</CardDescription>
</div>
<FlaskConical className="h-5 w-5 text-muted-foreground" />
</div>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm text-muted-foreground">
{exp.description.map((item, itemIndex) => (
<li key={itemIndex} className="flex items-start gap-2">
<span className="mt-2 h-1 w-1 flex-shrink-0 rounded-full bg-muted-foreground" />
{item}
</li>
))}
</ul>
</CardContent>
</Card>
</div>
))}
</div>
</section>
{/* Professional Experience Highlights */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">
Professional Experience Highlights
</h2>
<div className="space-y-6">
{professionalExperience.slice(0, 2).map((exp, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${index + 1}`}
>
<Card className="card-hover">
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="mb-1">{exp.title}</CardTitle>
<CardDescription>{exp.organization}</CardDescription>
<CardDescription className="text-xs">
{exp.location} {exp.period}
</CardDescription>
</div>
<Building className="h-5 w-5 text-muted-foreground" />
</div>
</CardHeader>
<CardContent>
<ul className="space-y-2 text-sm text-muted-foreground">
{exp.description.slice(0, 3).map((item, itemIndex) => (
<li key={itemIndex} className="flex items-start gap-2">
<span className="mt-2 h-1 w-1 flex-shrink-0 rounded-full bg-muted-foreground" />
{item}
</li>
))}
</ul>
</CardContent>
</Card>
</div>
))}
</div>
</section>
{/* Teaching Experience */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Teaching Experience</h2>
<div className="grid-equal-height grid gap-6 md:grid-cols-2">
{teachingExperience.slice(0, 4).map((exp, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${index + 1}`}
>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<GraduationCap className="h-5 w-5 flex-shrink-0" />
<div className="min-w-0">
<CardTitle className="mb-1 break-words text-base leading-tight">
{exp.title}
</CardTitle>
<CardDescription className="break-words text-xs">
{exp.organization}
</CardDescription>
<CardDescription className="text-xs">
{exp.period}
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<p className="text-sm leading-relaxed text-muted-foreground">
{exp.description[0]}
</p>
</CardContent>
</Card>
</div>
))}
</div>
</section>
{/* Leadership & Activities */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Leadership & Activities</h2>
<div className="grid-equal-height grid gap-6 md:grid-cols-2">
{leadershipExperience.map((exp, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${index + 1}`}
>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<Users className="h-5 w-5 flex-shrink-0" />
<div className="min-w-0">
<CardTitle className="mb-1 break-words text-base leading-tight">
{exp.title}
</CardTitle>
<CardDescription className="break-words text-xs">
{exp.organization}
</CardDescription>
<CardDescription className="text-xs">
{exp.period}
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<ul className="space-y-1 text-sm text-muted-foreground">
{exp.description.slice(0, 2).map((item, itemIndex) => (
<li key={itemIndex} className="flex items-start gap-2">
<span className="mt-2 h-1 w-1 flex-shrink-0 rounded-full bg-muted-foreground" />
<span className="break-words">{item}</span>
</li>
))}
</ul>
</CardContent>
</Card>
</div>
))}
</div>
</section>
{/* Recent Awards & Recognition */}
<section className="animate-fade-in-up space-y-6">
<h2 className="text-2xl font-bold">Recent Awards & Recognition</h2>
<div className="grid-equal-height grid gap-4 md:grid-cols-2">
{awards.map((award, index) => (
<div
key={index}
className={`animate-fade-in-up-delay-${index + 1}`}
>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<Award className="h-5 w-5 flex-shrink-0" />
<div className="min-w-0">
<CardTitle className="mb-1 break-words text-base leading-tight">
{award.title}
</CardTitle>
{award.organization && (
<CardDescription className="break-words">
{award.organization} {award.year}
</CardDescription>
)}
</div>
</div>
</CardHeader>
{award.description && (
<CardContent className="card-content-stretch">
<p className="break-words text-sm leading-relaxed text-muted-foreground">
{award.description}
</p>
</CardContent>
)} )}
</Card> </CardContent>
</div> </Card>
))} ))}
</div> </div>
</section> </section>
{/* Quick Links */} {/* Explore */}
<section className="animate-fade-in-up space-y-6"> <section className="animate-fade-in-up space-y-4">
<h2 className="text-2xl font-bold">Explore More</h2> <SectionLabel>Explore</SectionLabel>
<div className="grid-equal-height grid gap-6 md:grid-cols-3"> <div className="grid-equal-height grid gap-4 md:grid-cols-3">
<div className="animate-fade-in-up-delay-1">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<Code className="h-5 w-5 flex-shrink-0" />
<CardTitle className="mb-1 break-words">Projects</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col">
<p className="break-words leading-relaxed text-muted-foreground">
Explore my featured projects including HRIStudio, machine
learning research, and web applications.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/projects">
View Projects
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
</div>
<div className="animate-fade-in-up-delay-2"> <Card className="card-hover card-full-height">
<Card className="card-hover card-full-height"> <CardHeader>
<CardHeader> <div className="flex items-center gap-2">
<div className="flex items-center gap-2"> <Code className="h-5 w-5 flex-shrink-0" />
<BookOpen className="h-5 w-5 flex-shrink-0" /> <CardTitle>Projects</CardTitle>
<CardTitle className="mb-1 break-words"> </div>
Publications </CardHeader>
</CardTitle> <CardContent className="card-content-stretch">
</div> <div className="flex h-full flex-col gap-3">
</CardHeader> <p className="text-sm leading-relaxed text-muted-foreground">
<CardContent className="card-content-stretch"> HRIStudio, Riverhead Raceway, machine learning, embedded
<div className="flex h-full flex-col"> systems, and more.
<p className="break-words leading-relaxed text-muted-foreground"> </p>
Read my peer-reviewed publications in human-robot <Button variant="outline" asChild className="mt-auto w-full">
interaction research. <Link href="/projects">
</p> View projects
<Button variant="outline" asChild className="mt-auto w-full"> <ArrowUpRight className="ml-2 h-4 w-4" />
<Link href="/publications"> </Link>
View Publications </Button>
<ArrowUpRight className="ml-2 h-4 w-4" /> </div>
</Link> </CardContent>
</Button> </Card>
</div>
</CardContent> <Card className="card-hover card-full-height">
</Card> <CardHeader>
</div> <div className="flex items-center gap-2">
<BookOpen className="h-5 w-5 flex-shrink-0" />
<CardTitle>Publications</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Peer-reviewed work in human-robot interaction research.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/publications">
View publications
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<ExternalLink className="h-5 w-5 flex-shrink-0" />
<CardTitle>CV</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col gap-3">
<p className="text-sm leading-relaxed text-muted-foreground">
Complete academic and professional curriculum vitae.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/cv">
View CV
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
<div className="animate-fade-in-up-delay-3">
<Card className="card-hover card-full-height">
<CardHeader>
<div className="flex items-center gap-2">
<ExternalLink className="h-5 w-5 flex-shrink-0" />
<CardTitle className="mb-1 break-words">
Complete CV
</CardTitle>
</div>
</CardHeader>
<CardContent className="card-content-stretch">
<div className="flex h-full flex-col">
<p className="break-words leading-relaxed text-muted-foreground">
View my complete academic and professional curriculum vitae.
</p>
<Button variant="outline" asChild className="mt-auto w-full">
<Link href="/cv">
View CV
<ArrowUpRight className="ml-2 h-4 w-4" />
</Link>
</Button>
</div>
</CardContent>
</Card>
</div>
</div> </div>
</section> </section>
</div> </div>
); );
} }