Update projects page, add animations
This commit is contained in:
+75
-57
@@ -1,66 +1,84 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { ArrowUpRight, Newspaper } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "~/components/ui/card";
|
||||
import { CardSkeleton } from "~/components/ui/skeletons";
|
||||
import { useState, useEffect } from 'react';
|
||||
import { articles } from '~/lib/data';
|
||||
import { useState, useEffect } from "react";
|
||||
import { articles } from "~/lib/data";
|
||||
|
||||
export default function ArticlesPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchArticles = async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
setLoading(false);
|
||||
};
|
||||
fetchArticles();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const fetchArticles = async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
setLoading(false);
|
||||
};
|
||||
fetchArticles();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
<section className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">In the Media 📰</h1>
|
||||
<p className="text-lg text-muted-foreground mt-2">
|
||||
I have been lucky enough to have a few wonderful articles written about me and my work. Go check them out!
|
||||
</p>
|
||||
</div>
|
||||
{loading ? (
|
||||
<>
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</>
|
||||
) : (
|
||||
articles.map((article, index) => (
|
||||
<Card key={index}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{article.title}</CardTitle>
|
||||
<Link
|
||||
href={article.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
</Link>
|
||||
</div>
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
Written by {article.author}, found in {article.source}.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{article.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</section>
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<Newspaper className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">In the Media</h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
I have been lucky enough to have a few wonderful articles written
|
||||
about me and my work. Go check them out!
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="animate-fade-in-up-delay-2 space-y-6">
|
||||
{loading ? (
|
||||
<>
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
<CardSkeleton />
|
||||
</>
|
||||
) : (
|
||||
articles.map((article, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`animate-fade-in-up-delay-${Math.min(index + 3, 4)} card-hover`}
|
||||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{article.title}</CardTitle>
|
||||
<Link
|
||||
href={article.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
</Link>
|
||||
</div>
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
Written by {article.author}, found in {article.source}.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{article.description}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+35
-30
@@ -10,6 +10,7 @@ import {
|
||||
CardDescription,
|
||||
} from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
||||
import {
|
||||
Download,
|
||||
ExternalLink,
|
||||
@@ -21,6 +22,8 @@ import {
|
||||
ChevronRight,
|
||||
AlertCircle,
|
||||
Loader2,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
Eye,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -220,7 +223,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
const downloadBlob = () => {
|
||||
if (!pdfBlob) return;
|
||||
|
||||
const blob = new Blob([pdfBlob], { type: "application/pdf" });
|
||||
const blob = new Blob([pdfBlob as BlobPart], { type: "application/pdf" });
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
@@ -235,7 +238,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Action Bar */}
|
||||
<div className="bg-muted/50 flex items-center justify-between gap-4 rounded-lg border p-4">
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg border bg-muted/50 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
@@ -273,7 +276,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Action Bar */}
|
||||
<div className="bg-muted/50 flex items-center justify-between gap-4 rounded-lg border p-4">
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg border bg-muted/50 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5 text-muted-foreground" />
|
||||
<h3 className="font-medium">{title}</h3>
|
||||
@@ -348,7 +351,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Action Bar */}
|
||||
<div className="bg-muted/50 flex items-center justify-between gap-4 rounded-lg border p-4">
|
||||
<div className="flex items-center justify-between gap-4 rounded-lg border bg-muted/50 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileText className="h-5 w-5 text-muted-foreground" />
|
||||
<h3 className="font-medium">{title}</h3>
|
||||
@@ -436,7 +439,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
>
|
||||
{isLoading && (
|
||||
<>
|
||||
<div className="bg-background/80 absolute inset-0 z-10 flex items-center justify-center backdrop-blur-sm">
|
||||
<div className="absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Loader2 className="h-6 w-6 animate-spin text-primary" />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -464,7 +467,7 @@ function PDFViewer({ url, title, type }: PDFViewerProps) {
|
||||
</div>
|
||||
|
||||
{/* Mobile Notice */}
|
||||
<div className="block rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-950/50 md:hidden">
|
||||
<div className="block rounded-lg border border-amber-200 bg-amber-50 p-4 md:hidden dark:border-amber-800 dark:bg-amber-950/50">
|
||||
<div className="flex items-start gap-2">
|
||||
<AlertCircle className="h-5 w-5 text-amber-600 dark:text-amber-400" />
|
||||
<div>
|
||||
@@ -487,7 +490,7 @@ export default function CVPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<section className="prose prose-zinc dark:prose-invert max-w-none">
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<FileText className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
@@ -500,33 +503,35 @@ export default function CVPage() {
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="space-y-6"
|
||||
>
|
||||
<TabsList className="grid w-fit grid-cols-2">
|
||||
<TabsTrigger value="cv" className="gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
Academic CV
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="resume" className="gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
Resume
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="space-y-6"
|
||||
>
|
||||
<TabsList className="grid w-fit grid-cols-2">
|
||||
<TabsTrigger value="cv" className="gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
Academic CV
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="resume" className="gap-2">
|
||||
<FileText className="h-4 w-4" />
|
||||
Resume
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="cv" className="space-y-0">
|
||||
<PDFViewer url={CV_URL} title="Academic CV" type="cv" />
|
||||
</TabsContent>
|
||||
<TabsContent value="cv" className="space-y-0">
|
||||
<PDFViewer url={CV_URL} title="Academic CV" type="cv" />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="resume" className="space-y-0">
|
||||
<PDFViewer url={RESUME_URL} title="Resume" type="resume" />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<TabsContent value="resume" className="space-y-0">
|
||||
<PDFViewer url={RESUME_URL} title="Resume" type="resume" />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* Last Updated */}
|
||||
<div className="text-center text-xs text-muted-foreground">
|
||||
<div className="animate-fade-in-up-delay-4 text-center text-xs text-muted-foreground">
|
||||
Last updated: {new Date().toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+268
-234
@@ -23,14 +23,16 @@ export default function HomePage() {
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
{/* Hero Section */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-3xl font-bold">Sean O'Connor</h1>
|
||||
<p className="text-xl text-muted-foreground">
|
||||
<h1 className="animate-fade-in-up-delay-1 text-3xl font-bold">
|
||||
Sean O'Connor
|
||||
</h1>
|
||||
<p className="animate-fade-in-up-delay-2 text-xl text-muted-foreground">
|
||||
Computer Science and Engineering student with experience in software
|
||||
development, robotics research, and technical leadership.
|
||||
</p>
|
||||
<div className="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">
|
||||
<Mail className="h-4 w-4" />
|
||||
<a href="mailto:sean@soconnor.dev" className="hover:text-primary">
|
||||
@@ -46,7 +48,7 @@ export default function HomePage() {
|
||||
Lewisburg, PA
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3">
|
||||
<div className="animate-fade-in-up-delay-4 flex gap-3">
|
||||
<Button asChild>
|
||||
<Link href="/cv">
|
||||
View CV
|
||||
@@ -61,289 +63,321 @@ export default function HomePage() {
|
||||
</section>
|
||||
|
||||
{/* Current Focus */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="text-2xl font-bold">Current Focus</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<FlaskConical className="h-5 w-5" />
|
||||
<CardTitle className="mb-1">
|
||||
Human-Robot Interaction Research
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Developing a web-based platform for HRI experiments that
|
||||
addresses reproducibility challenges in Wizard-of-Oz studies.
|
||||
Published at IEEE RO-MAN 2024 with second publication
|
||||
forthcoming.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-1">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<FlaskConical className="h-5 w-5" />
|
||||
<CardTitle className="mb-1">
|
||||
Human-Robot Interaction Research
|
||||
</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Developing a web-based platform for HRI experiments that
|
||||
addresses reproducibility challenges in Wizard-of-Oz studies.
|
||||
Published at IEEE RO-MAN 2024 with second publication
|
||||
forthcoming.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<GraduationCap className="h-5 w-5" />
|
||||
<CardTitle className="mb-1">Academic Excellence</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Bachelor of Science in Computer Science and Engineering at
|
||||
Bucknell University. 3.86 Engineering GPA, Dean's List
|
||||
multiple semesters. Expected graduation: May 2026.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<GraduationCap className="h-5 w-5" />
|
||||
<CardTitle className="mb-1">Academic Excellence</CardTitle>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Bachelor of Science in Computer Science and Engineering at
|
||||
Bucknell University. 3.86 Engineering GPA, Dean's List
|
||||
multiple semesters. Expected graduation: May 2026.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Experience Highlights */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="text-2xl font-bold">Experience Highlights</h2>
|
||||
<div className="space-y-6">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Software Developer - Riverhead Raceway
|
||||
</CardTitle>
|
||||
<CardDescription>Oct 2020 – Present</CardDescription>
|
||||
<div className="animate-fade-in-up-delay-1">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Software Developer - Riverhead Raceway
|
||||
</CardTitle>
|
||||
<CardDescription>Oct 2020 – Present</CardDescription>
|
||||
</div>
|
||||
<Building className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<Building className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Transformed organizational culture by building trust in
|
||||
data-driven decision making. Revolutionized fan engagement
|
||||
through a real-time statistics platform serving 1500+ concurrent
|
||||
users. Modernized entire technical infrastructure through
|
||||
containerization and automated systems.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Transformed organizational culture by building trust in
|
||||
data-driven decision making. Revolutionized fan engagement
|
||||
through a real-time statistics platform serving 1500+
|
||||
concurrent users. Modernized entire technical infrastructure
|
||||
through containerization and automated systems.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Computer Science Researcher - Bucknell University
|
||||
</CardTitle>
|
||||
<CardDescription>Jan 2023 – Present</CardDescription>
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Computer Science Researcher - Bucknell University
|
||||
</CardTitle>
|
||||
<CardDescription>Jan 2023 – Present</CardDescription>
|
||||
</div>
|
||||
<FlaskConical className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<FlaskConical className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Led research and authored first-author paper presented at
|
||||
international conference. Built framework that enables
|
||||
researchers to conduct experiments across different robot
|
||||
platforms without specialized programming knowledge.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Led research and authored first-author paper presented at
|
||||
international conference. Built framework that enables
|
||||
researchers to conduct experiments across different robot
|
||||
platforms without specialized programming knowledge.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Teaching Assistant - Computer Science
|
||||
</CardTitle>
|
||||
<CardDescription>Jan 2024 – Present</CardDescription>
|
||||
<div className="animate-fade-in-up-delay-3">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="mb-1">
|
||||
Teaching Assistant - Computer Science
|
||||
</CardTitle>
|
||||
<CardDescription>Jan 2024 – Present</CardDescription>
|
||||
</div>
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<Users className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Mentored 150+ students in software engineering principles,
|
||||
connecting theoretical concepts to real-world applications.
|
||||
Developed learning environments that embrace productive failure.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Mentored 150+ students in software engineering principles,
|
||||
connecting theoretical concepts to real-world applications.
|
||||
Developed learning environments that embrace productive
|
||||
failure.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Technical Skills */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold">
|
||||
<Code className="h-6 w-6" />
|
||||
Technical Skills
|
||||
</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Languages & Frameworks</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Java, C/C++, Python, JavaScript/TypeScript, React, Next.js, PHP,
|
||||
SQL
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-1">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Languages & Frameworks</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Java, C/C++, Python, JavaScript/TypeScript, React, Next.js,
|
||||
PHP, SQL
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Backend & DevOps</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
REST APIs, MySQL, PostgreSQL, Docker, Apache Web Server, NGINX,
|
||||
ROS2
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Backend & DevOps</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
REST APIs, MySQL, PostgreSQL, Docker, Apache Web Server,
|
||||
NGINX, ROS2
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Cloud & Infrastructure</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
AWS, GCP, Azure, Backblaze, Linux (RHEL/Debian), CI/CD
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-3">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Cloud & Infrastructure</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
AWS, GCP, Azure, Backblaze, Linux (RHEL/Debian), CI/CD
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Development Tools</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Git, JetBrains Suite, VS Code, Cursor, Linux CLI
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-4">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Development Tools</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Git, JetBrains Suite, VS Code, Cursor, Linux CLI
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Leadership & Activities */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="flex items-center gap-2 text-2xl font-bold">
|
||||
<Users className="h-6 w-6" />
|
||||
Leadership & Activities
|
||||
</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">
|
||||
AIChE Chem-E-Car Competition Team
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Former President, Electrical and Mechanical Team Lead
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Pioneered team's first custom hardware solution by
|
||||
designing and fabricating a microcontroller-based control
|
||||
system. Improved team dynamics by introducing agile development
|
||||
principles and structured communication protocols.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-1">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">
|
||||
AIChE Chem-E-Car Competition Team
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Former President, Electrical and Mechanical Team Lead
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Pioneered team's first custom hardware solution by
|
||||
designing and fabricating a microcontroller-based control
|
||||
system. Improved team dynamics by introducing agile
|
||||
development principles and structured communication protocols.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Bucknell Coffee Society</CardTitle>
|
||||
<CardDescription>Treasurer</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Co-established and launched a new campus organization, managing
|
||||
financial operations and coordinating event logistics.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">Bucknell Coffee Society</CardTitle>
|
||||
<CardDescription>Treasurer</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Co-established and launched a new campus organization,
|
||||
managing financial operations and coordinating event
|
||||
logistics.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">RoboLab@Bucknell</CardTitle>
|
||||
<CardDescription>Founding Member</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Led and participated in group discussions in a new lab bridging
|
||||
computer science and psychology perspectives on human-robot
|
||||
interaction, working with the complexities of human-robot trust,
|
||||
job replacement, and autonomy.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<div className="animate-fade-in-up-delay-3">
|
||||
<Card className="card-hover">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="mb-1">RoboLab@Bucknell</CardTitle>
|
||||
<CardDescription>Founding Member</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-muted-foreground">
|
||||
Led and participated in group discussions in a new lab
|
||||
bridging computer science and psychology perspectives on
|
||||
human-robot interaction, working with the complexities of
|
||||
human-robot trust, job replacement, and autonomy.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Quick Links */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="text-2xl font-bold">Explore More</h2>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/publications" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<FlaskConical className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Research Publications</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
IEEE conferences and ongoing research
|
||||
<div className="animate-fade-in-up-delay-1">
|
||||
<Card className="card-hover group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/publications" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<FlaskConical className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Research Publications</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
IEEE conferences and ongoing research
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/projects" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Code className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Projects</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Software development and research work
|
||||
<div className="animate-fade-in-up-delay-2">
|
||||
<Card className="card-hover group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/projects" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Code className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Projects</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Software development and research work
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/cv" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<GraduationCap className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Curriculum Vitae</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Complete academic and professional record
|
||||
<div className="animate-fade-in-up-delay-3">
|
||||
<Card className="card-hover group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link href="/cv" className="block p-4">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<GraduationCap className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Curriculum Vitae</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Complete academic and professional record
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
<ArrowUpRight className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { Metadata } from "next";
|
||||
import { projects } from "~/lib/data";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import {
|
||||
Eye,
|
||||
Keyboard,
|
||||
Palette,
|
||||
Volume2,
|
||||
Globe,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
ExternalLink,
|
||||
Accessibility,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Accessibility Features",
|
||||
@@ -9,223 +23,412 @@ export const metadata: Metadata = {
|
||||
};
|
||||
|
||||
export default function AccessibilityPage() {
|
||||
// Find the Accessibility project data
|
||||
const project = projects.find((p) => p.title === "Accessibility Features");
|
||||
|
||||
return (
|
||||
<div className="container pb-6 pt-0">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="mb-4 text-4xl font-bold tracking-tight">
|
||||
{project?.title}
|
||||
</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
{project?.longDescription}
|
||||
</p>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{project?.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
<div className="space-y-8">
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<Accessibility className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">{project?.title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
Building an inclusive web experience for all users
|
||||
</p>
|
||||
|
||||
<div className="mt-8 space-y-8">
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Why Accessibility Matters
|
||||
</h2>
|
||||
<p className="mt-4">
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{project?.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Why Accessibility Matters */}
|
||||
<section className="animate-fade-in-up-delay-2 space-y-4">
|
||||
<h2 className="text-2xl font-bold">Why Accessibility Matters</h2>
|
||||
<Card>
|
||||
<CardContent className="space-y-4 p-6">
|
||||
<p className="text-muted-foreground">
|
||||
As a portfolio website aimed at showcasing my technical skills and
|
||||
projects to potential employers and collaborators, ensuring
|
||||
accessibility is not just a legal or ethical requirement—
|
||||
it's a demonstration of my professional competence and
|
||||
inclusive design thinking. Here's why accessibility is
|
||||
particularly important for my website:
|
||||
inclusive design thinking.
|
||||
</p>
|
||||
<ul className="mt-4 list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Universal Access:</strong> A portfolio should be
|
||||
accessible to all potential viewers, including hiring managers,
|
||||
colleagues, and collaborators who may have disabilities or
|
||||
situational limitations.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Professional Credibility:</strong> As a computer science
|
||||
and engineering student, demonstrating knowledge of
|
||||
accessibility standards reflects technical competence and
|
||||
attention to detail that employers value.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technical Showcase:</strong> Implementing accessibility
|
||||
features serves as a practical example of the technical skills
|
||||
being presented.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Academic Integrity:</strong> In an academic context,
|
||||
ensuring that educational resources (like the LaTeX tutorial)
|
||||
are accessible to all students reflects a commitment to
|
||||
educational equity.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Accessibility Features Implemented
|
||||
</h2>
|
||||
<div className="mt-4 space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
1. Comprehensive Image Alt Text
|
||||
</h3>
|
||||
<p className="mt-2">
|
||||
Every image on my website has been carefully evaluated and
|
||||
provided with appropriate alt text. I distinguish between:
|
||||
</p>
|
||||
<ul className="my-2 list-disc pl-6">
|
||||
<li>
|
||||
<strong>Decorative images:</strong> Images that are purely
|
||||
decorative, such as the LaTeX tutorial thumbnail, have alt
|
||||
text that indicates their decorative nature (e.g.,
|
||||
"Decorative thumbnail showing LaTeX code and formatting
|
||||
example").
|
||||
</li>
|
||||
<li>
|
||||
<strong>Informative images:</strong> Images that convey
|
||||
information, such as project screenshots, have detailed alt
|
||||
text describing their content (e.g., "Screenshot of
|
||||
HRIStudio application showing the robot control dashboard on
|
||||
a laptop").
|
||||
</li>
|
||||
<h3 className="mb-2 font-semibold">Professional Impact</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Demonstrates technical competence</li>
|
||||
<li>• Shows attention to detail</li>
|
||||
<li>• Reflects inclusive design thinking</li>
|
||||
<li>• Increases potential audience reach</li>
|
||||
</ul>
|
||||
<strong>Implementation details:</strong> I implemented this by
|
||||
adding custom "imageAlt" properties to all projects
|
||||
and "alts" arrays for travel items in my data
|
||||
structure, ensuring consistent image descriptions throughout the
|
||||
site.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
2. Accessible Video Player
|
||||
</h3>
|
||||
<p className="mt-2">
|
||||
I developed a custom video player for the LaTeX tutorial with
|
||||
several accessibility features:
|
||||
</p>
|
||||
<ul className="my-2 list-disc pl-6">
|
||||
<li>Closed captions that can be toggled on/off</li>
|
||||
<li>
|
||||
Full transcript available with expandable details element
|
||||
</li>
|
||||
<li>
|
||||
Keyboard-accessible controls for play/pause, volume, and
|
||||
caption toggling
|
||||
</li>
|
||||
<li>
|
||||
ARIA labels for all controls to improve screen reader
|
||||
compatibility
|
||||
</li>
|
||||
<li>Video poster image with appropriate alt text</li>
|
||||
<h3 className="mb-2 font-semibold">Academic Responsibility</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Ensures universal access to content</li>
|
||||
<li>• Supports educational equity</li>
|
||||
<li>• Serves as practical skills example</li>
|
||||
<li>• Demonstrates social awareness</li>
|
||||
</ul>
|
||||
<strong>Implementation details:</strong> I created a custom
|
||||
AccessibleVideo component that wraps the HTML5 video element
|
||||
with additional accessibility features, including captions
|
||||
integration, keyboard navigation, and proper ARIA attributes.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
3. Semantic HTML and ARIA
|
||||
</h3>
|
||||
<p className="mt-2">
|
||||
My website uses semantic HTML throughout to ensure proper
|
||||
structure and meaning:
|
||||
</p>
|
||||
<ul className="my-2 list-disc pl-6">
|
||||
<li>
|
||||
Proper heading hierarchy (h1, h2, h3) for logical document
|
||||
structure
|
||||
</li>
|
||||
<li>
|
||||
Semantic elements like <nav>, <main>,
|
||||
<section>, and <article>
|
||||
</li>
|
||||
<li>
|
||||
ARIA attributes for components without native semantics
|
||||
</li>
|
||||
<li>Skip-to-content links for keyboard users</li>
|
||||
</ul>
|
||||
<strong>Implementation details:</strong> I audited all pages for
|
||||
proper heading structure and added semantic HTML elements. For
|
||||
custom components like cards and badges, I ensured proper ARIA
|
||||
attributes were used to convey their purpose and state.
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold">
|
||||
4. Color Contrast and Dark Mode
|
||||
</h3>
|
||||
<p className="mt-2">
|
||||
I carefully selected color choices on my website to ensure
|
||||
readability:
|
||||
</p>
|
||||
<ul className="my-2 list-disc pl-6">
|
||||
<li>
|
||||
All text meets WCAG AA contrast requirements (4.5:1 for
|
||||
normal text, 3:1 for large text)
|
||||
</li>
|
||||
<li>
|
||||
Dark mode support for users who prefer or require reduced
|
||||
brightness
|
||||
</li>
|
||||
<li>
|
||||
Color is never used as the sole means of conveying
|
||||
information
|
||||
</li>
|
||||
<li>
|
||||
Interactive elements have visual focus indicators that
|
||||
don't rely solely on color
|
||||
</li>
|
||||
</ul>
|
||||
<strong>Implementation details:</strong> I used the TailwindCSS
|
||||
color palette with careful consideration of contrast ratios. The
|
||||
dark mode implementation respects user system preferences and
|
||||
provides consistent contrast ratios in both modes.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-2xl font-bold tracking-tight">
|
||||
Platform Limitations and Workarounds
|
||||
</h2>
|
||||
<p className="mt-4">
|
||||
While building this website, I encountered some limitations in the
|
||||
Next.js framework and implemented workarounds:
|
||||
</p>
|
||||
<ul className="mt-4 list-disc space-y-2 pl-6">
|
||||
<li>
|
||||
<strong>Next.js Client/Server Components:</strong> Next.js
|
||||
divides components into client and server components, which can
|
||||
create hydration issues when implementing certain interactive
|
||||
accessibility features. I addressed this by creating client-side
|
||||
wrapper components for features requiring interactivity, such as
|
||||
the video player and navigation menu.
|
||||
</li>
|
||||
<li>
|
||||
<strong>PDF Accessibility:</strong> The CV section uses PDF
|
||||
rendering which has inherent accessibility limitations. As a
|
||||
workaround, I provide the same information in HTML format
|
||||
elsewhere on the site, ensuring that users who cannot access the
|
||||
PDF can still view the content.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
{/* Accessibility Features */}
|
||||
<section className="animate-fade-in-up-delay-3 space-y-6">
|
||||
<h2 className="text-2xl font-bold">Implemented Features</h2>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Image Alt Text */}
|
||||
<div className="animate-fade-in-up-delay-4 card-hover">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Eye className="h-5 w-5" />
|
||||
Image Accessibility
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
All images include descriptive alt text for screen readers
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-4 card-hover">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Keyboard className="h-5 w-5" />
|
||||
Keyboard Navigation
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Full keyboard support for all interactive elements
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Additional Features */}
|
||||
<section className="space-y-6">
|
||||
<h2 className="text-2xl font-bold">Additional Features</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{/* Video Accessibility */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Volume2 className="h-5 w-5" />
|
||||
Video Accessibility
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Custom video player with comprehensive accessibility features.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Closed captions with toggle</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Full transcript available</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Keyboard-accessible controls</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Semantic HTML */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Globe className="h-5 w-5" />
|
||||
Semantic Structure
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Proper HTML structure and ARIA attributes for screen readers.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Logical heading hierarchy</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Semantic HTML elements</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">ARIA labels and attributes</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Color and Contrast */}
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Palette className="h-5 w-5" />
|
||||
Color & Contrast
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 pt-0">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Carefully selected colors ensuring readability for all users.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">WCAG AA contrast compliance</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">System dark mode support</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2">
|
||||
<CheckCircle className="mt-0.5 h-4 w-4 flex-shrink-0 text-green-600" />
|
||||
<div className="text-sm">Color-independent information</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Implementation Details */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Technical Implementation</h2>
|
||||
<div className="space-y-4">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle>Custom Components</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<p className="mb-3 text-sm text-muted-foreground">
|
||||
Built custom accessible components to ensure consistent user
|
||||
experience across the site.
|
||||
</p>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
<div className="rounded border p-3">
|
||||
<strong className="text-sm">AccessibleVideo</strong>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Video player with captions, transcript, and keyboard
|
||||
navigation
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded border p-3">
|
||||
<strong className="text-sm">Image Alt System</strong>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Structured data with imageAlt properties for all project
|
||||
images
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle>Standards Compliance</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-2 flex h-12 w-12 items-center justify-center bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300">
|
||||
<CheckCircle className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="font-medium">WCAG 2.1</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Level AA compliance
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-2 flex h-12 w-12 items-center justify-center bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300">
|
||||
<Keyboard className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="font-medium">Keyboard Navigation</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Full keyboard support
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="mx-auto mb-2 flex h-12 w-12 items-center justify-center bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300">
|
||||
<Volume2 className="h-6 w-6" />
|
||||
</div>
|
||||
<div className="font-medium">Screen Readers</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Optimized for assistive tech
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Challenges and Limitations */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Challenges & Solutions</h2>
|
||||
<Card>
|
||||
<CardContent className="space-y-4 p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-500" />
|
||||
<div>
|
||||
<h3 className="font-semibold">Next.js Hydration Issues</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Client/server component division created challenges for
|
||||
interactive accessibility features.
|
||||
</p>
|
||||
<p className="mt-2 text-sm">
|
||||
<strong>Solution:</strong> Created client-side wrapper
|
||||
components for interactive features while maintaining SSR
|
||||
benefits.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="mt-0.5 h-5 w-5 flex-shrink-0 text-amber-500" />
|
||||
<div>
|
||||
<h3 className="font-semibold">
|
||||
PDF Accessibility Limitations
|
||||
</h3>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
PDF rendering has inherent accessibility challenges for
|
||||
screen readers.
|
||||
</p>
|
||||
<p className="mt-2 text-sm">
|
||||
<strong>Solution:</strong> Provided equivalent information
|
||||
in HTML format throughout the site for users who cannot
|
||||
access PDFs.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Resources */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Accessibility Resources</h2>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link
|
||||
href="https://www.w3.org/WAI/WCAG21/quickref/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block p-4"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<CheckCircle className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">WCAG Quick Reference</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Official guidelines and techniques
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link
|
||||
href="https://webaim.org/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block p-4"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Eye className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">WebAIM</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Accessibility testing and resources
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Continuous Improvement */}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-2xl font-bold">Ongoing Commitment</h2>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
<p className="text-muted-foreground">
|
||||
Accessibility is not a one-time implementation but an ongoing
|
||||
commitment to inclusive design. This site continues to evolve
|
||||
with user feedback and updated standards.
|
||||
</p>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold">Regular Testing</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Screen reader compatibility testing</li>
|
||||
<li>• Keyboard navigation verification</li>
|
||||
<li>• Color contrast validation</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold">Future Enhancements</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• User preference settings</li>
|
||||
<li>• Enhanced keyboard shortcuts</li>
|
||||
<li>• Voice navigation support</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/projects">
|
||||
<Eye className="mr-2 h-4 w-4" />
|
||||
View All Projects
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { Metadata } from "next";
|
||||
import { projects } from "~/lib/data";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { AccessibleVideo } from "~/components/AccessibleVideo";
|
||||
import { ExternalLink, Play, BookOpen, BookOpenText, Code } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "LaTeX Introduction Tutorial",
|
||||
description: "A comprehensive 5-minute introduction to LaTeX document preparation system for beginners.",
|
||||
description:
|
||||
"A comprehensive 5-minute introduction to LaTeX document preparation system for beginners.",
|
||||
};
|
||||
|
||||
const transcript = `
|
||||
@@ -24,58 +29,266 @@ const transcript = `
|
||||
`;
|
||||
|
||||
export default function LatexTutorialPage() {
|
||||
// Find the LaTeX project data
|
||||
const project = projects.find((p) => p.title === "LaTeX Introduction Tutorial");
|
||||
const project = projects.find(
|
||||
(p) => p.title === "LaTeX Introduction Tutorial",
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="container pt-0 pb-6">
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold tracking-tight mb-4">{project?.title}</h1>
|
||||
<p className="text-lg text-muted-foreground">{project?.longDescription}</p>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{project?.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
<div className="space-y-8">
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<BookOpenText className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">{project?.title}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
A beginner-friendly introduction to LaTeX document preparation system
|
||||
</p>
|
||||
|
||||
<div className="mt-8">
|
||||
<AccessibleVideo
|
||||
src="/videos/latex-intro.mp4"
|
||||
poster="/latex-thumbnail.jpg"
|
||||
captionSrc="/videos/latex-intro.vtt"
|
||||
title="LaTeX Introduction Tutorial"
|
||||
description="A 5-minute introduction to LaTeX, covering basic syntax, document structure, and common use cases."
|
||||
transcript={transcript}
|
||||
posterAlt="Decorative thumbnail showing LaTeX code and formatting example"
|
||||
/>
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{project?.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="mt-8 space-y-6">
|
||||
<h2 className="text-2xl font-bold tracking-tight">Why Learn LaTeX?</h2>
|
||||
<p>LaTeX is a document preparation system widely used in academia, especially in fields like mathematics, computer science, physics, and engineering. It excels at:</p>
|
||||
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li>Professional typesetting of mathematical equations</li>
|
||||
<li>Consistent document formatting</li>
|
||||
<li>Automated handling of citations and references</li>
|
||||
<li>Version control compatibility</li>
|
||||
<li>Cross-platform document creation</li>
|
||||
</ul>
|
||||
|
||||
<p>This tutorial provides a gentle introduction to get you started with your first LaTeX document.</p>
|
||||
|
||||
<h2 className="text-2xl font-bold tracking-tight">Key Resources</h2>
|
||||
<ul className="list-disc pl-6 space-y-2">
|
||||
<li><a href="https://www.overleaf.com" className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">Overleaf</a> - A popular online LaTeX editor</li>
|
||||
<li><a href="https://www.latex-project.org/get/" className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">LaTeX Project</a> - Official downloads for local installation</li>
|
||||
<li><a href="https://en.wikibooks.org/wiki/LaTeX" className="text-primary hover:underline" target="_blank" rel="noopener noreferrer">LaTeX Wikibook</a> - Comprehensive reference guide</li>
|
||||
</ul>
|
||||
{/* Video Section */}
|
||||
<section className="animate-fade-in-up-delay-2 space-y-4">
|
||||
<h2 className="text-2xl font-bold">Tutorial Video</h2>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<AccessibleVideo
|
||||
src="/videos/latex-intro.mp4"
|
||||
poster="/latex-thumbnail.jpg"
|
||||
captionSrc="/videos/latex-intro.vtt"
|
||||
title="LaTeX Introduction Tutorial"
|
||||
description="A 5-minute introduction to LaTeX, covering basic syntax, document structure, and common use cases."
|
||||
transcript={transcript}
|
||||
posterAlt="Decorative thumbnail showing LaTeX code and formatting example"
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* What You'll Learn */}
|
||||
<section className="animate-fade-in-up-delay-3 space-y-4">
|
||||
<h2 className="text-2xl font-bold">What You'll Learn</h2>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="animate-fade-in-up-delay-4 card-hover">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Play className="h-5 w-5" />
|
||||
Getting Started
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Setting up Overleaf</li>
|
||||
<li>• Basic document structure</li>
|
||||
<li>• Common formatting commands</li>
|
||||
<li>• Creating your first document</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div className="animate-fade-in-up-delay-4 card-hover">
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Code className="h-5 w-5" />
|
||||
Core Features
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Mathematical equations</li>
|
||||
<li>• Document organization</li>
|
||||
<li>• Bibliography management</li>
|
||||
<li>• Professional typesetting</li>
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Why LaTeX */}
|
||||
<section className="animate-fade-in-up-delay-4 space-y-4">
|
||||
<h2 className="text-2xl font-bold">Why LaTeX?</h2>
|
||||
<Card>
|
||||
<CardContent className="space-y-4 p-6">
|
||||
<p className="text-muted-foreground">
|
||||
LaTeX is the gold standard for academic and technical document
|
||||
preparation, especially in mathematics, computer science, physics,
|
||||
and engineering fields.
|
||||
</p>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold">Academic Excellence</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Professional mathematical typesetting</li>
|
||||
<li>• Consistent document formatting</li>
|
||||
<li>• Automated citations and references</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="mb-2 font-semibold">Technical Benefits</h3>
|
||||
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||
<li>• Version control friendly</li>
|
||||
<li>• Cross-platform compatibility</li>
|
||||
<li>• Separation of content and design</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
|
||||
{/* Resources */}
|
||||
<section className="animate-fade-in-up-delay-4 space-y-4">
|
||||
<h2 className="text-2xl font-bold">Essential Resources</h2>
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link
|
||||
href="https://www.overleaf.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block p-4"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Play className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">Overleaf</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Online LaTeX editor
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link
|
||||
href="https://www.latex-project.org/get/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block p-4"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<Code className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">LaTeX Project</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Official downloads
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
|
||||
<Card className="group cursor-pointer transition-colors hover:bg-accent">
|
||||
<Link
|
||||
href="https://en.wikibooks.org/wiki/LaTeX"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block p-4"
|
||||
>
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<BookOpen className="h-6 w-6 text-primary" />
|
||||
<div>
|
||||
<div className="font-medium">LaTeX Wikibook</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Comprehensive guide
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ExternalLink className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Next Steps */}
|
||||
<section className="animate-fade-in-up-delay-4 space-y-4">
|
||||
<h2 className="text-2xl font-bold">Next Steps</h2>
|
||||
<Card>
|
||||
<CardContent className="p-6">
|
||||
<div className="space-y-4">
|
||||
<p className="text-muted-foreground">
|
||||
Ready to start your LaTeX journey? Here's what to do next:
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-6 w-6 items-center justify-center bg-primary text-xs font-bold text-primary-foreground">
|
||||
1
|
||||
</div>
|
||||
<span className="text-sm">
|
||||
Create a free account on Overleaf
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-6 w-6 items-center justify-center bg-primary text-xs font-bold text-primary-foreground">
|
||||
2
|
||||
</div>
|
||||
<span className="text-sm">
|
||||
Watch the tutorial video above
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-6 w-6 items-center justify-center bg-primary text-xs font-bold text-primary-foreground">
|
||||
3
|
||||
</div>
|
||||
<span className="text-sm">
|
||||
Practice with a simple document
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-6 w-6 items-center justify-center bg-primary text-xs font-bold text-primary-foreground">
|
||||
4
|
||||
</div>
|
||||
<span className="text-sm">
|
||||
Explore the LaTeX Wikibook for advanced features
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-4">
|
||||
<Button asChild>
|
||||
<Link
|
||||
href="https://www.overleaf.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Get Started with Overleaf
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+152
-207
@@ -11,7 +11,7 @@ import {
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRight, Play, BookOpen, Code } from "lucide-react";
|
||||
import { ArrowUpRight, Play, BookOpen, FolderGit2 } from "lucide-react";
|
||||
import { projects } from "~/lib/data";
|
||||
import Image from "next/image";
|
||||
import { CardSkeleton } from "~/components/ui/skeletons";
|
||||
@@ -28,23 +28,21 @@ export default function ProjectsPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{/* Header */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Code className="h-8 w-8 text-primary" />
|
||||
<section className="prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<FolderGit2 className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">Projects</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
A collection of my academic research, professional work, and
|
||||
personal projects spanning robotics, web development, and embedded
|
||||
systems.
|
||||
</p>
|
||||
<h1 className="mb-2 text-2xl font-bold">Projects</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
A collection of my academic research, professional work, and personal
|
||||
projects spanning robotics, web development, and embedded systems.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Featured Projects */}
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="text-2xl font-bold">Featured Work</h2>
|
||||
<div className="space-y-8">
|
||||
{loading ? (
|
||||
@@ -55,38 +53,31 @@ export default function ProjectsPage() {
|
||||
</>
|
||||
) : (
|
||||
featuredProjects.map((project, index) => (
|
||||
<Card key={index} className="overflow-hidden">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
{/* Project Image */}
|
||||
{project.image && (
|
||||
<div className="relative lg:w-2/5">
|
||||
<div className="aspect-[16/10] lg:aspect-square">
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.imageAlt || project.title}
|
||||
width={600}
|
||||
height={400}
|
||||
className="h-full w-full object-cover"
|
||||
priority={index === 0}
|
||||
/>
|
||||
{project.title === "LaTeX Introduction Tutorial" && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20">
|
||||
<div className="flex h-16 w-16 items-center justify-center bg-white/90 transition-transform hover:scale-110">
|
||||
<Play
|
||||
className="h-8 w-8 text-primary"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
key={index}
|
||||
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
|
||||
>
|
||||
<Card className="overflow-hidden">
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
{/* Project Image */}
|
||||
{project.image && (
|
||||
<div className="lg:w-1/3">
|
||||
<div className="flex items-center justify-center p-4 lg:h-full">
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.imageAlt || project.title}
|
||||
width={400}
|
||||
height={300}
|
||||
className="h-auto w-full object-contain"
|
||||
priority={index === 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Project Content */}
|
||||
<div className="flex flex-1 flex-col justify-between p-6">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start justify-between">
|
||||
{/* Project Content */}
|
||||
<div className="flex flex-1 flex-col p-6">
|
||||
<div className="flex-1 space-y-4">
|
||||
<div>
|
||||
<CardTitle className="text-xl">
|
||||
{project.title}
|
||||
@@ -95,67 +86,59 @@ export default function ProjectsPage() {
|
||||
{project.description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
{project.link && !project.link.startsWith("/") && (
|
||||
<Link
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="ml-4 text-muted-foreground transition-colors hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<p className="text-muted-foreground">
|
||||
{project.longDescription}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground">
|
||||
{project.longDescription}
|
||||
</p>
|
||||
<div className="mt-6 flex items-center justify-between gap-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<Badge key={tag} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
<div className="flex gap-2">
|
||||
{project.link && project.link.startsWith("/") && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={project.link}>
|
||||
{project.title ===
|
||||
"LaTeX Introduction Tutorial" ? (
|
||||
<>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
Watch Tutorial
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BookOpen className="mr-2 h-4 w-4" />
|
||||
Learn More
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{project.link && !project.link.startsWith("/") && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ArrowUpRight className="mr-2 h-4 w-4" />
|
||||
View Project
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="mt-6 flex flex-wrap gap-3">
|
||||
{project.link && project.link.startsWith("/") && (
|
||||
<Button asChild>
|
||||
<Link href={project.link}>
|
||||
{project.title === "LaTeX Introduction Tutorial" ? (
|
||||
<>
|
||||
<Play className="mr-2 h-4 w-4" />
|
||||
Watch Tutorial
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<BookOpen className="mr-2 h-4 w-4" />
|
||||
Learn More
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{project.link && !project.link.startsWith("/") && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ArrowUpRight className="mr-2 h-4 w-4" />
|
||||
View Project
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
@@ -163,7 +146,7 @@ export default function ProjectsPage() {
|
||||
|
||||
{/* Other Projects */}
|
||||
{otherProjects.length > 0 && (
|
||||
<section className="space-y-6">
|
||||
<section className="animate-fade-in-up space-y-6">
|
||||
<h2 className="text-2xl font-bold">Additional Projects</h2>
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
{loading ? (
|
||||
@@ -173,122 +156,84 @@ export default function ProjectsPage() {
|
||||
</>
|
||||
) : (
|
||||
otherProjects.map((project, index) => (
|
||||
<Card key={index} className="flex flex-col">
|
||||
{project.image && (
|
||||
<div className="relative aspect-[16/10] overflow-hidden">
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.imageAlt || project.title}
|
||||
width={400}
|
||||
height={250}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 flex-col p-6">
|
||||
<CardHeader className="p-0">
|
||||
<div className="flex items-start justify-between">
|
||||
<CardTitle className="text-lg">
|
||||
{project.title}
|
||||
</CardTitle>
|
||||
{project.link && !project.link.startsWith("/") && (
|
||||
<Link
|
||||
href={project.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground transition-colors hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<CardDescription className="mt-2">
|
||||
{project.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="flex-1 p-0 pt-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<Badge
|
||||
key={tag}
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
{project.link && (
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
asChild
|
||||
className="w-full"
|
||||
>
|
||||
<Link
|
||||
href={project.link}
|
||||
{...(!project.link.startsWith("/") && {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
})}
|
||||
>
|
||||
{project.link.startsWith("/") ? (
|
||||
<>
|
||||
<BookOpen className="mr-2 h-4 w-4" />
|
||||
Learn More
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArrowUpRight className="mr-2 h-4 w-4" />
|
||||
View Project
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
<div
|
||||
key={index}
|
||||
className={`animate-fade-in-up-delay-${Math.min(index + 1, 4)} card-hover`}
|
||||
>
|
||||
<Card className="flex flex-col">
|
||||
{project.image && (
|
||||
<div className="flex h-48 items-center justify-center p-4">
|
||||
<Image
|
||||
src={project.image}
|
||||
alt={project.imageAlt || project.title}
|
||||
width={400}
|
||||
height={250}
|
||||
className="h-auto max-h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="flex flex-1 flex-col p-6">
|
||||
<div className="flex-1">
|
||||
<CardHeader className="p-0">
|
||||
<div>
|
||||
<CardTitle className="text-lg">
|
||||
{project.title}
|
||||
</CardTitle>
|
||||
</div>
|
||||
<CardDescription className="mt-2">
|
||||
{project.description}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 pt-4">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{project.tags.map((tag) => (
|
||||
<Badge
|
||||
key={tag}
|
||||
variant="secondary"
|
||||
className="text-xs"
|
||||
>
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{project.link && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link
|
||||
href={project.link}
|
||||
{...(!project.link.startsWith("/") && {
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer",
|
||||
})}
|
||||
>
|
||||
{project.link.startsWith("/") ? (
|
||||
<>
|
||||
<BookOpen className="mr-2 h-4 w-4" />
|
||||
Learn More
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ArrowUpRight className="mr-2 h-4 w-4" />
|
||||
View Project
|
||||
</>
|
||||
)}
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Project Stats */}
|
||||
<section className="border-t pt-8">
|
||||
<div className="grid gap-4 md:grid-cols-3">
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">{projects.length}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Total Projects
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{[...new Set(projects.flatMap((p) => p.tags))].length}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Technologies</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="text-center">
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{projects.filter((p) => p.featured).length}
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Featured</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+103
-94
@@ -81,17 +81,21 @@ export default function PublicationsPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<section className="prose prose-zinc dark:prose-invert max-w-none">
|
||||
<h1 className="flex items-center gap-2 text-2xl font-bold">
|
||||
<BookOpen className="h-6 w-6" />
|
||||
Peer-Reviewed Publications
|
||||
</h1>
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<BookOpenText className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">
|
||||
Peer-Reviewed Publications
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
My research publications in human-robot interaction and robotics.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div className="animate-fade-in-up-delay-2 space-y-6">
|
||||
{loading ? (
|
||||
<>
|
||||
<CardSkeleton />
|
||||
@@ -100,97 +104,102 @@ export default function PublicationsPage() {
|
||||
</>
|
||||
) : (
|
||||
publications.map((pub, index) => (
|
||||
<Card key={index}>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{pub.title}</CardTitle>
|
||||
{pub.paperUrl && (
|
||||
<Link
|
||||
href={pub.paperUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<CardDescription className="text-base">
|
||||
{pub.authors.join(", ")}
|
||||
</CardDescription>
|
||||
<CardDescription className="text-sm">
|
||||
{pub.venue} ({pub.year})
|
||||
</CardDescription>
|
||||
{pub.address && (
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
{pub.address}
|
||||
<div
|
||||
key={index}
|
||||
className={`animate-fade-in-up-delay-${Math.min(index + 3, 4)} card-hover`}
|
||||
>
|
||||
<Card>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>{pub.title}</CardTitle>
|
||||
{pub.paperUrl && (
|
||||
<Link
|
||||
href={pub.paperUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary"
|
||||
>
|
||||
<ArrowUpRight className="h-5 w-5" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<CardDescription className="text-base">
|
||||
{pub.authors.join(", ")}
|
||||
</CardDescription>
|
||||
)}
|
||||
{pub.notes && (
|
||||
<div className="mt-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{pub.notes}
|
||||
<CardDescription className="text-sm">
|
||||
{pub.venue} ({pub.year})
|
||||
</CardDescription>
|
||||
{pub.address && (
|
||||
<CardDescription className="text-sm text-muted-foreground">
|
||||
{pub.address}
|
||||
</CardDescription>
|
||||
)}
|
||||
{pub.notes && (
|
||||
<div className="mt-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{pub.notes}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{pub.abstract && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{pub.abstract}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
{pub.type}
|
||||
</Badge>
|
||||
{pub.doi && (
|
||||
<Link
|
||||
href={`https://doi.org/${pub.doi}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<ArrowUpRight className="mr-1 h-3 w-3" />
|
||||
DOI
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
{pub.paperUrl && (
|
||||
<Link
|
||||
href={pub.paperUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<FileText className="mr-1 h-3 w-3" />
|
||||
Paper
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
{pub.posterUrl && (
|
||||
<Link
|
||||
href={pub.posterUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<Presentation className="mr-1 h-3 w-3" />
|
||||
Poster
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
<Badge
|
||||
onClick={() => downloadBibtex(pub)}
|
||||
className="cursor-pointer capitalize"
|
||||
variant="secondary"
|
||||
>
|
||||
<BookOpenText className="mr-1 h-3 w-3" />
|
||||
BibTeX
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent className="pt-0">
|
||||
{pub.abstract && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{pub.abstract}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
{pub.type}
|
||||
</Badge>
|
||||
{pub.doi && (
|
||||
<Link
|
||||
href={`https://doi.org/${pub.doi}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<ArrowUpRight className="mr-1 h-3 w-3" />
|
||||
DOI
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
{pub.paperUrl && (
|
||||
<Link
|
||||
href={pub.paperUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<FileText className="mr-1 h-3 w-3" />
|
||||
Paper
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
{pub.posterUrl && (
|
||||
<Link
|
||||
href={pub.posterUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Badge variant="secondary" className="capitalize">
|
||||
<Presentation className="mr-1 h-3 w-3" />
|
||||
Poster
|
||||
</Badge>
|
||||
</Link>
|
||||
)}
|
||||
<Badge
|
||||
onClick={() => downloadBibtex(pub)}
|
||||
className="cursor-pointer capitalize"
|
||||
variant="secondary"
|
||||
>
|
||||
<BookOpenText className="mr-1 h-3 w-3" />
|
||||
BibTeX
|
||||
</Badge>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
+52
-40
@@ -11,7 +11,9 @@ import {
|
||||
import { CardSkeleton } from "~/components/ui/skeletons";
|
||||
import Image from "next/image";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
import { Plane } from "lucide-react";
|
||||
import { travel } from "~/lib/data";
|
||||
|
||||
export default function TripsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
@@ -24,14 +26,19 @@ export default function TripsPage() {
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<section className="prose prose-zinc dark:prose-invert max-w-none">
|
||||
<h1 className="text-2xl font-bold">My Trips & Events 🌍</h1>
|
||||
<section className="animate-fade-in-up prose prose-zinc dark:prose-invert max-w-none">
|
||||
<div className="flex items-start gap-3">
|
||||
<Plane className="h-8 w-8 text-primary" />
|
||||
<div>
|
||||
<h1 className="mb-2 text-2xl font-bold">My Trips & Events</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-2 text-lg text-muted-foreground">
|
||||
A collection of memorable trips and events I've attended.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="animate-fade-in-up-delay-2 grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{loading ? (
|
||||
<>
|
||||
<CardSkeleton />
|
||||
@@ -43,45 +50,50 @@ export default function TripsPage() {
|
||||
</>
|
||||
) : (
|
||||
travel.map((trip, index) => (
|
||||
<Card key={index} className="overflow-hidden rounded-lg">
|
||||
<CardHeader className="p-0">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex space-x-0 overflow-x-auto">
|
||||
{trip.images.map((image, imgIndex) => (
|
||||
<div key={imgIndex} className="flex-shrink-0">
|
||||
<Image
|
||||
src={image}
|
||||
alt={
|
||||
trip.alts && trip.alts[imgIndex]
|
||||
? trip.alts[imgIndex]
|
||||
: `${trip.title} - image ${imgIndex + 1}`
|
||||
}
|
||||
width={250}
|
||||
height={200}
|
||||
className="max-h-[200px] min-h-[200px] object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
key={index}
|
||||
className={`animate-fade-in-up-delay-${Math.min(index + 3, 4)} card-hover`}
|
||||
>
|
||||
<Card className="overflow-hidden rounded-lg">
|
||||
<CardHeader className="p-0">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex space-x-0 overflow-x-auto">
|
||||
{trip.images.map((image, imgIndex) => (
|
||||
<div key={imgIndex} className="flex-shrink-0">
|
||||
<Image
|
||||
src={image}
|
||||
alt={
|
||||
trip.alts && trip.alts[imgIndex]
|
||||
? trip.alts[imgIndex]
|
||||
: `${trip.title} - image ${imgIndex + 1}`
|
||||
}
|
||||
width={250}
|
||||
height={200}
|
||||
className="max-h-[200px] min-h-[200px] object-cover"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col content-between justify-items-start">
|
||||
<CardTitle className="mb-2 mt-6">{trip.title}</CardTitle>
|
||||
<CardDescription className="">
|
||||
{trip.description}
|
||||
</CardDescription>
|
||||
{/* Show badges for tags */}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{trip.tags.map((tag, tagIndex) => (
|
||||
<Badge key={tagIndex} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex flex-col content-between justify-items-start">
|
||||
<CardTitle className="mb-2 mt-6">{trip.title}</CardTitle>
|
||||
<CardDescription className="">
|
||||
{trip.description}
|
||||
</CardDescription>
|
||||
{/* Show badges for tags */}
|
||||
<div className="mt-4 flex flex-wrap gap-2">
|
||||
{trip.tags.map((tag, tagIndex) => (
|
||||
<Badge key={tagIndex} variant="secondary">
|
||||
{tag}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user