Enhance layout and project components; improve accessibility and content

- Added BreadcrumbWrapper to the layout for better navigation.
- Updated ProjectsPage to include CardFooter with conditional rendering for project links.
- Improved image alt text for better accessibility in travel and project sections.
- Added new project details and alt text in data.ts for enhanced content clarity.
This commit is contained in:
2025-04-06 20:18:52 -04:00
parent 8a13b5a166
commit 282ed0b0ad
16 changed files with 2801 additions and 18 deletions

File diff suppressed because one or more lines are too long

1333
bun.lock Normal file

File diff suppressed because it is too large Load Diff

BIN
public/latex-thumbnail.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

View File

@@ -0,0 +1,472 @@
1
00:00:01,280 --> 00:00:05,600
Hello, and welcome to getting started
2
00:00:03,040 --> 00:00:06,960
with LaTeX. This tutorial will walk you
3
00:00:05,600 --> 00:00:09,160
through creating your first document
4
00:00:06,960 --> 00:00:12,800
using the LaTeX
5
00:00:09,160 --> 00:00:14,719
system. Alright, so what is LaTeX? LaTeX
6
00:00:12,800 --> 00:00:16,680
is a typesetting system commonly used
7
00:00:14,719 --> 00:00:18,640
for technical and scientific
8
00:00:16,680 --> 00:00:20,640
documents. It's widely used throughout
9
00:00:18,640 --> 00:00:23,199
the world of academia specifically in
10
00:00:20,640 --> 00:00:25,600
engineering and science fields. This is
11
00:00:23,199 --> 00:00:27,920
due to how easy LaTeX makes it for someone
12
00:00:25,600 --> 00:00:30,960
to include complex mathematical
13
00:00:27,920 --> 00:00:32,880
equations, models and more. Most documents
14
00:00:30,960 --> 00:00:34,920
written in math or physics courses here
15
00:00:32,880 --> 00:00:37,360
at Bucknell are written using
16
00:00:34,920 --> 00:00:39,360
LaTeX. Throughout this tutorial we'll be
17
00:00:37,360 --> 00:00:43,440
utilizing a free LaTeX editor called
18
00:00:39,360 --> 00:00:43,440
Overleaf which is available online at
19
00:00:44,360 --> 00:00:48,640
overleaf.com. All right, so let's set up
20
00:00:46,399 --> 00:00:51,280
an editor. We'll start by going to
21
00:00:48,640 --> 00:00:52,800
Overleaf and signing in. If you don't
22
00:00:51,280 --> 00:00:54,520
have an account, register using your
23
00:00:52,800 --> 00:00:57,199
school provided Google
24
00:00:54,520 --> 00:00:58,960
account. Once you see this page, click the
25
00:00:57,199 --> 00:01:00,879
create a new project button and select
26
00:00:58,960 --> 00:01:03,840
blank project. You'll be sent to the
27
00:01:00,879 --> 00:01:06,000
Overleaf editor. This is where we'll be
28
00:01:03,840 --> 00:01:08,479
working on our documents. The editor has
29
00:01:06,000 --> 00:01:11,280
three main segments: the file selector,
30
00:01:08,479 --> 00:01:12,799
code editor, and preview pane. When
31
00:01:11,280 --> 00:01:14,640
editing code, you'll need to hit the
32
00:01:12,799 --> 00:01:16,439
recompile button to see changes on the
33
00:01:14,640 --> 00:01:18,960
preview
34
00:01:16,439 --> 00:01:21,520
pane. Let's talk about some commonly used
35
00:01:18,960 --> 00:01:23,840
tags. We begin with the title, author, and
36
00:01:21,520 --> 00:01:25,680
date tags. These specify important
37
00:01:23,840 --> 00:01:27,479
information about the document and their
38
00:01:25,680 --> 00:01:29,520
specifics are
39
00:01:27,479 --> 00:01:31,280
self-explanatory. Next we have the begin
40
00:01:29,520 --> 00:01:33,000
tag which tells the compiler to start
41
00:01:31,280 --> 00:01:35,520
building the document
42
00:01:33,000 --> 00:01:37,600
itself. The make title tag tells the
43
00:01:35,520 --> 00:01:40,200
compiler to format and display the title
44
00:01:37,600 --> 00:01:42,880
information including the author and the
45
00:01:40,200 --> 00:01:44,479
date. The section tag signifies the
46
00:01:42,880 --> 00:01:48,159
beginning of a section and produces a
47
00:01:44,479 --> 00:01:49,920
numbered section heading.
48
00:01:48,159 --> 00:01:53,520
Next we'll talk about tags that can be
49
00:01:49,920 --> 00:01:57,200
used to format text. The textbf tag
50
00:01:53,520 --> 00:01:58,759
makes text appear bold. The textit tag
51
00:01:57,200 --> 00:02:01,360
makes text appear
52
00:01:58,759 --> 00:02:04,799
italicized. The underline tag makes text
53
00:02:01,360 --> 00:02:06,880
appear underlined. The emph tag emphasizes a
54
00:02:04,799 --> 00:02:09,520
specific block of text. The appearance of
55
00:02:06,880 --> 00:02:11,360
this effect depends on the situation. If
56
00:02:09,520 --> 00:02:12,879
the text being emphasized is within a
57
00:02:11,360 --> 00:02:16,080
block of standard text, it will be
58
00:02:12,879 --> 00:02:18,000
italicized. Otherwise, if that selected
59
00:02:16,080 --> 00:02:19,959
text block is within other italicized
60
00:02:18,000 --> 00:02:21,920
text, it will be
61
00:02:19,959 --> 00:02:23,760
de-italicized. These effects can be
62
00:02:21,920 --> 00:02:26,080
combined within the same line. For
63
00:02:23,760 --> 00:02:28,720
example, this line of code will italicize
64
00:02:26,080 --> 00:02:31,440
the selected text as well as keep the
65
00:02:28,720 --> 00:02:31,440
surrounding text
66
00:02:31,480 --> 00:02:36,720
bold. Next we'll talk about displaying
67
00:02:33,920 --> 00:02:38,720
math within a LaTeX document. Math
68
00:02:36,720 --> 00:02:41,440
equations can be displayed in two modes:
69
00:02:38,720 --> 00:02:43,200
inline mode and display mode.
70
00:02:41,440 --> 00:02:45,360
Starting with inline mode, we use the
71
00:02:43,200 --> 00:02:48,000
begin math and end math tags to specify
72
00:02:45,360 --> 00:02:50,239
where an equation is written. This method
73
00:02:48,000 --> 00:02:51,840
places an equation inline, meaning that
74
00:02:50,239 --> 00:02:54,519
it will wrap as if it was part of the
75
00:02:51,840 --> 00:02:57,120
paragraph, blending with the surrounding
76
00:02:54,519 --> 00:02:59,120
text. On the other hand, we can use
77
00:02:57,120 --> 00:03:01,040
display mode which uses the begin
78
00:02:59,120 --> 00:03:03,840
equation and end equation tags to
79
00:03:01,040 --> 00:03:05,519
distinguish equations from text.
80
00:03:03,840 --> 00:03:08,640
This will display math as if it's its
81
00:03:05,519 --> 00:03:10,480
own line in a paragraph and can be used
82
00:03:08,640 --> 00:03:12,640
to separate equations from the rest of
83
00:03:10,480 --> 00:03:12,640
the
84
00:03:12,680 --> 00:03:18,000
text. Now we'll put it all together.
85
00:03:15,920 --> 00:03:20,319
Here's an example of a document, this one
86
00:03:18,000 --> 00:03:22,239
being the script for this video. Notice
87
00:03:20,319 --> 00:03:24,800
how the title, author, and date tags are
88
00:03:22,239 --> 00:03:26,879
used as well as the section headers. The
89
00:03:24,800 --> 00:03:28,280
title, author, and date are formatted in
90
00:03:26,879 --> 00:03:30,640
the center of the page
91
00:03:28,280 --> 00:03:33,120
automatically with text sizes being
92
00:03:30,640 --> 00:03:34,640
decided by the compiler. The section
93
00:03:33,120 --> 00:03:36,879
headings are automatically numbered and
94
00:03:34,640 --> 00:03:39,599
bolded and create coherent paragraphs
95
00:03:36,879 --> 00:03:41,680
with minimal effort. As you can see, the
96
00:03:39,599 --> 00:03:43,360
LaTeX system allows for an author to
97
00:03:41,680 --> 00:03:45,599
create a document without focusing on
98
00:03:43,360 --> 00:03:47,519
its formatting as one would see with
99
00:03:45,599 --> 00:03:50,080
other what-you-see-is-what-you-get
100
00:03:47,519 --> 00:03:53,040
editors such as Microsoft Word or Google
101
00:03:50,080 --> 00:03:55,200
Docs. Instead, the author can focus on the
102
00:03:53,040 --> 00:03:57,360
content knowing that the content will be
103
00:03:55,200 --> 00:03:59,599
presented in a coherent way that makes
104
00:03:57,360 --> 00:04:01,599
sense to the reader. When you're done
105
00:03:59,599 --> 00:04:04,400
writing the document or making any edits
106
00:04:01,599 --> 00:04:06,319
to it, you can click the recompile button
107
00:04:04,400 --> 00:04:08,799
and that will display a preview in the
108
00:04:06,319 --> 00:04:10,959
preview pane. When you want to download
109
00:04:08,799 --> 00:04:12,959
or export the document, click the
110
00:04:10,959 --> 00:04:15,920
download button next to the recompile
111
00:04:12,959 --> 00:04:19,160
button. That will export a PDF file and
112
00:04:15,920 --> 00:04:22,400
send it to your downloads
113
00:04:19,160 --> 00:04:24,080
folder. And that's it! Congratulations,
114
00:04:22,400 --> 00:04:26,160
you've just finished your first LaTeX
115
00:04:24,080 --> 00:04:28,320
document. I hope you've been able to
116
00:04:26,160 --> 00:04:30,400
learn from this video and you'll be able
117
00:04:28,320 --> 00:04:34,919
to recreate a LaTeX document whenever
118
00:04:30,400 --> 00:04:34,919
you need. Thank you for watching.

View File

@@ -0,0 +1,355 @@
WEBVTT
00:00:01.280 --> 00:00:05.600
Hello, and welcome to getting started
00:00:03.040 --> 00:00:06.960
with LaTeX. This tutorial will walk you
00:00:05.600 --> 00:00:09.160
through creating your first document
00:00:06.960 --> 00:00:12.800
using the LaTeX
00:00:09.160 --> 00:00:14.719
system. Alright, so what is LaTeX? LaTeX
00:00:12.800 --> 00:00:16.680
is a typesetting system commonly used
00:00:14.719 --> 00:00:18.640
for technical and scientific
00:00:16.680 --> 00:00:20.640
documents. It's widely used throughout
00:00:18.640 --> 00:00:23.199
the world of academia specifically in
00:00:20.640 --> 00:00:25.600
engineering and science fields. This is
00:00:23.199 --> 00:00:27.920
due to how easy LaTeX makes it for someone
00:00:25.600 --> 00:00:30.960
to include complex mathematical
00:00:27.920 --> 00:00:32.880
equations, models and more. Most documents
00:00:30.960 --> 00:00:34.920
written in math or physics courses here
00:00:32.880 --> 00:00:37.360
at Bucknell are written using
00:00:34.920 --> 00:00:39.360
LaTeX. Throughout this tutorial we'll be
00:00:37.360 --> 00:00:43.440
utilizing a free LaTeX editor called
00:00:39.360 --> 00:00:43.440
Overleaf which is available online at
00:00:44.360 --> 00:00:48.640
overleaf.com. All right, so let's set up
00:00:46.399 --> 00:00:51.280
an editor. We'll start by going to
00:00:48.640 --> 00:00:52.800
Overleaf and signing in. If you don't
00:00:51.280 --> 00:00:54.520
have an account, register using your
00:00:52.800 --> 00:00:57.199
school provided Google
00:00:54.520 --> 00:00:58.960
account. Once you see this page, click the
00:00:57.199 --> 00:01:00.879
create a new project button and select
00:00:58.960 --> 00:01:03.840
blank project. You'll be sent to the
00:01:00.879 --> 00:01:06.000
Overleaf editor. This is where we'll be
00:01:03.840 --> 00:01:08.479
working on our documents. The editor has
00:01:06.000 --> 00:01:11.280
three main segments: the file selector,
00:01:08.479 --> 00:01:12.799
code editor, and preview pane. When
00:01:11.280 --> 00:01:14.640
editing code, you'll need to hit the
00:01:12.799 --> 00:01:16.439
recompile button to see changes on the
00:01:14.640 --> 00:01:18.960
preview
00:01:16.439 --> 00:01:21.520
pane. Let's talk about some commonly used
00:01:18.960 --> 00:01:23.840
tags. We begin with the title, author, and
00:01:21.520 --> 00:01:25.680
date tags. These specify important
00:01:23.840 --> 00:01:27.479
information about the document and their
00:01:25.680 --> 00:01:29.520
specifics are
00:01:27.479 --> 00:01:31.280
self-explanatory. Next we have the begin
00:01:29.520 --> 00:01:33.000
tag which tells the compiler to start
00:01:31.280 --> 00:01:35.520
building the document
00:01:33.000 --> 00:01:37.600
itself. The make title tag tells the
00:01:35.520 --> 00:01:40.200
compiler to format and display the title
00:01:37.600 --> 00:01:42.880
information including the author and the
00:01:40.200 --> 00:01:44.479
date. The section tag signifies the
00:01:42.880 --> 00:01:48.159
beginning of a section and produces a
00:01:44.479 --> 00:01:49.920
numbered section heading.
00:01:48.159 --> 00:01:53.520
Next we'll talk about tags that can be
00:01:49.920 --> 00:01:57.200
used to format text. The textbf tag
00:01:53.520 --> 00:01:58.759
makes text appear bold. The textit tag
00:01:57.200 --> 00:02:01.360
makes text appear
00:01:58.759 --> 00:02:04.799
italicized. The underline tag makes text
00:02:01.360 --> 00:02:06.880
appear underlined. The emph tag emphasizes a
00:02:04.799 --> 00:02:09.520
specific block of text. The appearance of
00:02:06.880 --> 00:02:11.360
this effect depends on the situation. If
00:02:09.520 --> 00:02:12.879
the text being emphasized is within a
00:02:11.360 --> 00:02:16.080
block of standard text, it will be
00:02:12.879 --> 00:02:18.000
italicized. Otherwise, if that selected
00:02:16.080 --> 00:02:19.959
text block is within other italicized
00:02:18.000 --> 00:02:21.920
text, it will be
00:02:19.959 --> 00:02:23.760
de-italicized. These effects can be
00:02:21.920 --> 00:02:26.080
combined within the same line. For
00:02:23.760 --> 00:02:28.720
example, this line of code will italicize
00:02:26.080 --> 00:02:31.440
the selected text as well as keep the
00:02:28.720 --> 00:02:31.440
surrounding text
00:02:31.480 --> 00:02:36.720
bold. Next we'll talk about displaying
00:02:33.920 --> 00:02:38.720
math within a LaTeX document. Math
00:02:36.720 --> 00:02:41.440
equations can be displayed in two modes:
00:02:38.720 --> 00:02:43.200
inline mode and display mode.
00:02:41.440 --> 00:02:45.360
Starting with inline mode, we use the
00:02:43.200 --> 00:02:48.000
begin math and end math tags to specify
00:02:45.360 --> 00:02:50.239
where an equation is written. This method
00:02:48.000 --> 00:02:51.840
places an equation inline, meaning that
00:02:50.239 --> 00:02:54.519
it will wrap as if it was part of the
00:02:51.840 --> 00:02:57.120
paragraph, blending with the surrounding
00:02:54.519 --> 00:02:59.120
text. On the other hand, we can use
00:02:57.120 --> 00:03:01.040
display mode which uses the begin
00:02:59.120 --> 00:03:03.840
equation and end equation tags to
00:03:01.040 --> 00:03:05.519
distinguish equations from text.
00:03:03.840 --> 00:03:08.640
This will display math as if it's its
00:03:05.519 --> 00:03:10.480
own line in a paragraph and can be used
00:03:08.640 --> 00:03:12.640
to separate equations from the rest of
00:03:10.480 --> 00:03:12.640
the
00:03:12.680 --> 00:03:18.000
text. Now we'll put it all together.
00:03:15.920 --> 00:03:20.319
Here's an example of a document, this one
00:03:18.000 --> 00:03:22.239
being the script for this video. Notice
00:03:20.319 --> 00:03:24.800
how the title, author, and date tags are
00:03:22.239 --> 00:03:26.879
used as well as the section headers. The
00:03:24.800 --> 00:03:28.280
title, author, and date are formatted in
00:03:26.879 --> 00:03:30.640
the center of the page
00:03:28.280 --> 00:03:33.120
automatically with text sizes being
00:03:30.640 --> 00:03:34.640
decided by the compiler. The section
00:03:33.120 --> 00:03:36.879
headings are automatically numbered and
00:03:34.640 --> 00:03:39.599
bolded and create coherent paragraphs
00:03:36.879 --> 00:03:41.680
with minimal effort. As you can see, the
00:03:39.599 --> 00:03:43.360
LaTeX system allows for an author to
00:03:41.680 --> 00:03:45.599
create a document without focusing on
00:03:43.360 --> 00:03:47.519
its formatting as one would see with
00:03:45.599 --> 00:03:50.080
other what-you-see-is-what-you-get
00:03:47.519 --> 00:03:53.040
editors such as Microsoft Word or Google
00:03:50.080 --> 00:03:55.200
Docs. Instead, the author can focus on the
00:03:53.040 --> 00:03:57.360
content knowing that the content will be
00:03:55.200 --> 00:03:59.599
presented in a coherent way that makes
00:03:57.360 --> 00:04:01.599
sense to the reader. When you're done
00:03:59.599 --> 00:04:04.400
writing the document or making any edits
00:04:01.599 --> 00:04:06.319
to it, you can click the recompile button
00:04:04.400 --> 00:04:08.799
and that will display a preview in the
00:04:06.319 --> 00:04:10.959
preview pane. When you want to download
00:04:08.799 --> 00:04:12.959
or export the document, click the
00:04:10.959 --> 00:04:15.920
download button next to the recompile
00:04:12.959 --> 00:04:19.160
button. That will export a PDF file and
00:04:15.920 --> 00:04:22.400
send it to your downloads
00:04:19.160 --> 00:04:24.080
folder. And that's it! Congratulations,
00:04:22.400 --> 00:04:26.160
you've just finished your first LaTeX
00:04:24.080 --> 00:04:28.320
document. I hope you've been able to
00:04:26.160 --> 00:04:30.400
learn from this video and you'll be able
00:04:28.320 --> 00:04:34.919
to recreate a LaTeX document whenever
00:04:30.400 --> 00:04:34.919
you need. Thank you for watching.

View File

@@ -3,6 +3,8 @@ import { SpeedInsights } from "@vercel/speed-insights/next"
import { Footer } from "~/components/Footer"
import { Navigation } from "~/components/Navigation"
import { Sidebar } from "~/components/Sidebar"
import { BreadcrumbWrapper } from "~/components/BreadcrumbWrapper"
import { inter } from "~/lib/fonts"
import { description, name } from "~/lib/data";
import "~/styles/globals.css"
@@ -27,13 +29,14 @@ export default function RootLayout({ children }: React.PropsWithChildren) {
<Sidebar />
</aside>
<main className="flex-1 overflow-y-auto py-8">
<BreadcrumbWrapper />
{children}
</main>
</div>
</div>
</div>
<Footer />
</body>
</html >
</body>
</html>
)
}

16
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,16 @@
import Link from 'next/link'
export default function NotFound() {
return (
<div className="flex flex-col items-center justify-center min-h-[calc(100vh-200px)]">
<h2 className="text-3xl font-bold mb-4">404 - Not Found</h2>
<p className="mb-6">Could not find the requested resource</p>
<Link
href="/"
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
>
Return Home
</Link>
</div>
)
}

View File

@@ -0,0 +1,81 @@
import { Metadata } from "next";
import { projects } from "~/lib/data";
import { Badge } from "~/components/ui/badge";
import { AccessibleVideo } from "~/components/AccessibleVideo";
export const metadata: Metadata = {
title: "LaTeX Introduction Tutorial",
description: "A comprehensive 5-minute introduction to LaTeX document preparation system for beginners.",
};
const transcript = `
<p>Hello, and welcome to getting started with LaTeX. This tutorial will walk you through creating your first document using the LaTeX system.</p>
<p>LaTeX is a typesetting system commonly used for technical and scientific documents. It's widely used throughout the world of academia specifically in engineering and science fields.</p>
<p>This is due to how easy LaTeX makes it for someone to include complex mathematical equations, models and more. Most documents written in math or physics courses here at Bucknell are written using LaTeX.</p>
<p>Throughout this tutorial we'll be utilizing a free LaTeX editor called Overleaf which is available online at overleaf.com.</p>
<p>We'll go through:</p>
<ul>
<li>Setting up the editor</li>
<li>Commonly used tags and formatting</li>
<li>Working with math equations</li>
<li>Creating a complete document</li>
</ul>
<p>By the end of this tutorial, you'll be able to create your own professional-looking documents with proper formatting and mathematical notation.</p>
`;
export default function LatexTutorialPage() {
// Find the LaTeX project data
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>
</div>
<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>
<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>
</div>
</div>
</div>
);
}

View File

@@ -1,10 +1,11 @@
'use client';
import { useEffect, useState } from "react";
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "~/components/ui/card";
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from "~/components/ui/card";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import Link from "next/link";
import { ArrowUpRight } from "lucide-react";
import { ArrowUpRight, Play, BookOpen } from "lucide-react";
import { projects } from "~/lib/data";
import Image from "next/image";
import { CardSkeleton } from "~/components/ui/skeletons";
@@ -41,7 +42,7 @@ export default function ProjectsPage() {
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle>{project.title}</CardTitle>
{project.link && (
{project.link && !project.link.startsWith('/') && (
<Link
href={project.link}
target="_blank"
@@ -66,20 +67,53 @@ export default function ProjectsPage() {
))}
</div>
</CardContent>
{project.link && project.link.startsWith('/') && (
<CardFooter className="pt-0">
<Link href={project.link}>
<Button
variant="default"
size="sm"
className="mt-0"
>
{project.title === "LaTeX Introduction Tutorial" ? (
<>
<Play className="mr-2 h-4 w-4" />
Watch the LaTeX tutorial
</>
) : (
<>
<BookOpen className="mr-2 h-4 w-4" />
View project details
</>
)}
</Button>
</Link>
</CardFooter>
)}
</div>
{project.image && (
<div className="px-6 pb-6 lg:py-6 lg:w-1/3 md:px-24 lg:px-6">
<div className="relative aspect-[4/3] w-full overflow-hidden">
<Image
src={project.image}
alt={project.title}
width={400}
height={300}
className="object-contain w-full h-full"
priority={index === 0}
/>
</div>
<Link href={project.link?.startsWith('/') ? project.link : project.link || '#'}>
<div className="relative aspect-[4/3] w-full overflow-hidden rounded-md transition-all hover:opacity-90">
<Image
src={project.image}
alt={project.imageAlt || project.title}
width={400}
height={300}
className="object-cover w-full h-full"
priority={index === 0}
/>
{project.title === "LaTeX Introduction Tutorial" && (
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
<div className="rounded-full bg-white/80 p-3">
<Play className="h-8 w-8 text-primary" fill="currentColor" />
</div>
</div>
)}
</div>
</Link>
</div>
)}
</div>

View File

@@ -45,7 +45,7 @@ export default function TripsPage() {
<div key={imgIndex} className="flex-shrink-0">
<Image
src={image}
alt={trip.title}
alt={trip.alts && trip.alts[imgIndex] ? trip.alts[imgIndex] : `${trip.title} - image ${imgIndex + 1}`}
width={250}
height={200}
className="object-cover min-h-[200px] max-h-[200px]"

View File

@@ -0,0 +1,143 @@
"use client";
import { useRef, useState } from "react";
import { Card, CardContent } from "~/components/ui/card";
import { Button } from "~/components/ui/button";
import { Play, Pause, Volume2, VolumeX } from "lucide-react";
interface AccessibleVideoProps {
src: string;
poster?: string;
posterAlt?: string;
captionSrc?: string;
title: string;
description: string;
transcript?: string;
}
export function AccessibleVideo({
src,
poster,
posterAlt,
captionSrc,
title,
description,
transcript,
}: AccessibleVideoProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const [playing, setPlaying] = useState(false);
const [muted, setMuted] = useState(false);
const [captionsEnabled, setCaptionsEnabled] = useState(true);
const togglePlay = () => {
if (videoRef.current) {
if (playing) {
videoRef.current.pause();
} else {
void videoRef.current.play();
}
setPlaying(!playing);
}
};
const toggleMute = () => {
if (videoRef.current) {
videoRef.current.muted = !muted;
setMuted(!muted);
}
};
const toggleCaptions = () => {
if (videoRef.current && videoRef.current.textTracks.length > 0) {
const track = videoRef.current.textTracks[0];
if (track) {
track.mode = captionsEnabled ? "hidden" : "showing";
setCaptionsEnabled(!captionsEnabled);
}
}
};
return (
<div className="space-y-4">
<div aria-labelledby="video-title" aria-describedby="video-description">
<h2 id="video-title" className="sr-only">{title}</h2>
<p id="video-description" className="sr-only">{description}</p>
</div>
<Card className="overflow-hidden">
<CardContent className="p-0">
<div className="relative aspect-video w-full">
<video
ref={videoRef}
className="h-full w-full"
poster={poster}
onPlay={() => setPlaying(true)}
onPause={() => setPlaying(false)}
controls
src={src}
aria-label={title}
title={posterAlt || title}
>
{captionSrc && (
<track
kind="captions"
src={captionSrc}
label="English"
srcLang="en"
default={captionsEnabled}
/>
)}
Your browser does not support the video tag.
</video>
</div>
</CardContent>
</Card>
<div className="flex items-center justify-between">
<div className="flex gap-2">
<Button
onClick={togglePlay}
aria-label={playing ? "Pause video" : "Play video"}
variant="outline"
size="sm"
>
{playing ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
<span className="ml-2">{playing ? "Pause" : "Play"}</span>
</Button>
<Button
onClick={toggleMute}
aria-label={muted ? "Unmute video" : "Mute video"}
variant="outline"
size="sm"
>
{muted ? <VolumeX className="h-4 w-4" /> : <Volume2 className="h-4 w-4" />}
<span className="ml-2">{muted ? "Unmute" : "Mute"}</span>
</Button>
</div>
{captionSrc && (
<Button
onClick={toggleCaptions}
aria-label={captionsEnabled ? "Turn off captions" : "Turn on captions"}
variant="outline"
size="sm"
>
<span>{captionsEnabled ? "Captions On" : "Captions Off"}</span>
</Button>
)}
</div>
{transcript && (
<details className="mt-4">
<summary className="cursor-pointer font-medium text-primary">View Full Transcript</summary>
<div className="mt-2 rounded-md bg-muted p-4">
<div className="prose prose-sm dark:prose-invert max-w-none">
<div dangerouslySetInnerHTML={{ __html: transcript }} />
</div>
</div>
</details>
)}
</div>
);
}

View File

@@ -0,0 +1,12 @@
"use client";
import dynamic from "next/dynamic";
// Dynamically import PageBreadcrumb with no SSR to avoid hydration issues
const PageBreadcrumb = dynamic(() => import("~/components/PageBreadcrumb").then(mod => mod.PageBreadcrumb), {
ssr: false,
});
export function BreadcrumbWrapper() {
return <PageBreadcrumb />;
}

View File

@@ -0,0 +1,117 @@
"use client";
import * as React from "react";
import { usePathname } from "next/navigation";
import { Home, ChevronRight, Folder, BookOpen, Newspaper, Plane, FileText } from "lucide-react";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "~/components/ui/breadcrumb";
interface BreadcrumbItem {
href: string;
label: string;
icon: React.ReactNode;
isCurrentPage?: boolean;
}
export function PageBreadcrumb() {
const pathname = usePathname();
// Generate breadcrumb items based on current path
const breadcrumbItems: BreadcrumbItem[] = [
{
href: "/",
label: "Home",
icon: <Home className="h-3.5 w-3.5 mr-1" />,
}
];
// Parse path segments into breadcrumb items
const pathSegments = pathname.split('/').filter(segment => segment !== '');
if (pathSegments.length > 0) {
// Build paths incrementally for correct href values
let currentPath = "";
pathSegments.forEach((segment, index) => {
currentPath += `/${segment}`;
const isLastSegment = index === pathSegments.length - 1;
let label = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ');
let icon;
// Assign appropriate icons based on path
switch (segment) {
case 'projects':
icon = <Folder className="h-3.5 w-3.5 mr-1" />;
break;
case 'articles':
icon = <Newspaper className="h-3.5 w-3.5 mr-1" />;
break;
case 'publications':
icon = <BookOpen className="h-3.5 w-3.5 mr-1" />;
break;
case 'travel':
icon = <Plane className="h-3.5 w-3.5 mr-1" />;
break;
case 'cv':
icon = <FileText className="h-3.5 w-3.5 mr-1" />;
label = "CV";
break;
case 'latex-intro':
icon = <BookOpen className="h-3.5 w-3.5 mr-1" />;
label = "LaTeX Tutorial";
break;
default:
icon = <ChevronRight className="h-3.5 w-3.5 mr-1" />;
}
breadcrumbItems.push({
href: currentPath,
label,
icon,
isCurrentPage: isLastSegment,
});
});
}
// Don't show breadcrumbs on the home page
if (pathname === "/") {
return null;
}
return (
<div className="mb-6">
<Breadcrumb>
<BreadcrumbList className="flex items-center text-sm">
{breadcrumbItems.map((item, index) => (
<React.Fragment key={item.href}>
<BreadcrumbItem>
{item.isCurrentPage ? (
<BreadcrumbPage className="flex items-center">
{item.icon}
{item.label}
</BreadcrumbPage>
) : (
<BreadcrumbLink href={item.href} className="flex items-center">
{item.icon}
{item.label}
</BreadcrumbLink>
)}
</BreadcrumbItem>
{index < breadcrumbItems.length - 1 && (
<BreadcrumbSeparator />
)}
</React.Fragment>
))}
</BreadcrumbList>
</Breadcrumb>
</div>
);
}

View File

@@ -0,0 +1,115 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "~/lib/utils"
const Breadcrumb = React.forwardRef<
HTMLElement,
React.ComponentPropsWithoutRef<"nav"> & {
separator?: React.ReactNode
}
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"
const BreadcrumbList = React.forwardRef<
HTMLOListElement,
React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
<ol
ref={ref}
className={cn(
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
className
)}
{...props}
/>
))
BreadcrumbList.displayName = "BreadcrumbList"
const BreadcrumbItem = React.forwardRef<
HTMLLIElement,
React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
<li
ref={ref}
className={cn("inline-flex items-center gap-1.5", className)}
{...props}
/>
))
BreadcrumbItem.displayName = "BreadcrumbItem"
const BreadcrumbLink = React.forwardRef<
HTMLAnchorElement,
React.ComponentPropsWithoutRef<"a"> & {
asChild?: boolean
}
>(({ asChild, className, ...props }, ref) => {
const Comp = asChild ? Slot : "a"
return (
<Comp
ref={ref}
className={cn("transition-colors hover:text-foreground", className)}
{...props}
/>
)
})
BreadcrumbLink.displayName = "BreadcrumbLink"
const BreadcrumbPage = React.forwardRef<
HTMLSpanElement,
React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
<span
ref={ref}
role="link"
aria-disabled="true"
aria-current="page"
className={cn("font-normal text-foreground", className)}
{...props}
/>
))
BreadcrumbPage.displayName = "BreadcrumbPage"
const BreadcrumbSeparator = ({
children,
className,
...props
}: React.ComponentProps<"li">) => (
<li
role="presentation"
aria-hidden="true"
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
{...props}
>
{children ?? <ChevronRight />}
</li>
)
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
const BreadcrumbEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
role="presentation"
aria-hidden="true"
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More</span>
</span>
)
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
export {
Breadcrumb,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
BreadcrumbEllipsis,
}

View File

@@ -61,7 +61,7 @@ export const articles = [
title: "Positively Innovative: Robotics for Good",
link: "https://magazine.bucknell.edu/issue/fall-2024/robotics-for-good/",
author: "Kate Willard",
description: "Sean OConnor 26 is using his interest in robotics to fuel forward-thinking research and lead important conversations about the impact robots can have on society.",
description: "Sean O'Connor '26 is using his interest in robotics to fuel forward-thinking research and lead important conversations about the impact robots can have on society.",
source: "Bucknell Magazine (Fall 2024)"
},
{
@@ -89,6 +89,7 @@ export const projects = [
tags: ["ROS2", "React", "TypeScript", "C++", "Python"],
link: "https://github.com/soconnor0919/hristudio",
image: "/hristudio_laptop.png",
imageAlt: "Screenshot of HRIStudio application showing the robot control dashboard on a laptop",
featured: true
},
{
@@ -97,6 +98,17 @@ export const projects = [
longDescription: "Designed and developed a personal portfolio website using modern web technologies. Features include responsive design, dark mode support, PDF rendering for CV display, and a clean, professional interface for showcasing projects and experience.",
tags: ["Next.js", "TypeScript", "TailwindCSS", "React"],
link: "https://github.com/soconnor0919/personal-website",
imageAlt: "Screenshot of the personal website showing the homepage with project showcases",
featured: true
},
{
title: "LaTeX Introduction Tutorial",
description: "A 5-minute introduction to LaTeX document preparation system for academic and technical writing.",
longDescription: "Created an accessible tutorial video explaining LaTeX, a typesetting system commonly used for technical and scientific documents in academia. The video covers how to set up Overleaf as an editor, explains key LaTeX tags and formatting, demonstrates both inline and display math equations, and provides a complete walkthrough of creating your first document with proper formatting.",
tags: ["LaTeX", "Tutorial", "Accessibility", "Education", "Overleaf"],
link: "/projects/latex-intro",
image: "/latex-thumbnail.jpg",
imageAlt: "Decorative thumbnail showing the project title 'Getting Started with LaTeX'",
featured: true
},
{
@@ -114,6 +126,7 @@ export const projects = [
tags: ["C++", "Embedded Systems", "Hardware Design"],
link: "https://github.com/soconnor0919/national_fa24",
image: "/car.png",
imageAlt: "Photo of the Chem-E-Car with custom control system hardware visible, showing the microcontroller and sensor connections",
featured: true
},
];
@@ -123,36 +136,42 @@ export const travel = [
title: "AIChE Annual Student Conference 2024",
description: "With the funding of Bucknell's chemical engineering department, and an amazing team, I was able to attend the 2024 AIChE Annual Student Conference and compete in the national Chem-E-Car competition.",
images: ["/trips/asc2024/IMG_2641.png", "/trips/asc2024/IMG_2631.png", "/trips/asc2024/IMG_7987.png"],
alts: ["A photo of the Chem-E-Car team holding their trophies, dressed in their lab coats.", "A selfie of the Chem-E-Car team at their workstation.", "Sean discussing the components of the Chem-E-Car with a judge."],
tags: ["Chem-E-Car", "AIChE", "Conference", "Competition"]
},
{
title: "IEEE RO-MAN 2024",
description: "I got to attend the IEEE RO-MAN 2024 conference in Pasadena, California. It was a great opportunity to present my work on my project HRIStudio, and to network with other researchers and industry professionals.",
images: ["/trips/roman2024/IMG_3951.png", "/trips/roman2024/IMG_3978.png", "/trips/roman2024/IMG_3946.png"],
alts: ["A photo of Sean presenting his poster at RO-MAN 2024.", "A photo Felipe and Sean at dinner.", "A photo of Sean discussing his poster with a group of attendees."],
tags: ["RO-MAN", "IEEE", "Conference", "Presentation"]
},
{
title: "ENGR 290: Following da Vinci's Footsteps",
description: "During the summer of 2024, I went on a study abroad program with about thirty of my peers. We explored Italy and France, following the footsteps of Leonardo da Vinci- evaluating the world through his lenses.",
images: ["/trips/engr290/insta290.jpg", "/trips/engr290/P1013747.png", "/trips/engr290/_1024461.png"],
tags: ["Italy", "France", "Study Abroad", "Engineering"]
alts: ["A photo of the group taken during the final dinner of the trip.", "A photo of Florence, taken from the top of a distant hill.", "A photo of the River Seine in Paris, taken from the top of the Eiffel Tower."],
tags: ["Study Abroad", "Italy", "France", "da Vinci"]
},
{
title: "SCA Specialty Coffee Expo 2024",
description: "As a member of the executive board of the Bucknell Coffee Society, I was able to attend the Specialty Coffee Association's Specialty Coffee Expo in early 2024, traveling to Chicago, IL.",
images: ["/trips/sca2024/group.jpeg", "/trips/sca2024/bean.png", "/trips/sca2024/plane.png"],
alts: ["A photo of the group at the SCA Specialty Coffee Expo in Chicago.", "A photo of the Chicago Bean.", "A photo of Chicago's coastline, taken through the window of the plane."],
tags: ["Coffee Society", "Chicago", "SCA", "Coffee"]
},
{
title: "Formula 1 Gran Premio dell'Emilia Romagna 2024",
description: "While studying abroad with Bucknell Engineering, we were lucky enough to be within a few hours of the Imola Grand Prix! A group of students went to see the race, and it was an amazing experience.",
images: ["/trips/imola2024/IMG_2093.png", "/trips/imola2024/IMG_2050.png", "/trips/imola2024/IMG_2066.png"],
alts: ["A photo of the group at the Imola Circuit.", "A photo of flags on a fence, in honor of the late Ayrton Senna.", "A photo Lando Norris driving through the Imola Circuit, taken through a fence."],
tags: ["Racing", "Formula One", "Italy"]
},
{
title: "Formula 1 British Grand Prix 2024",
description: "As a semi-recent Formula One fan, I was very excited to have the opportunity to attend the British Grand Prix weekend in 2024. I was able to see every event- marking one of my favorite weekends, probably ever.",
images: ["/trips/silverstone/_1035852.png", "/trips/silverstone/P1025274.png", "/trips/silverstone/_1035764.png"],
alts: ["Sean and his professor sitting on a sign with the text #BritishGP.", "A close-up photo of the rear of the McLaren MCL38.", "A photo of Oscar Piastri taking a turn at Silverstone Circuit."],
tags: ["Racing", "Formula One", "Great Britain", "Silverstone"]
}
];