Skip to content

Commit 1a50a02

Browse files
committed
Add videos
1 parent 15a4b61 commit 1a50a02

25 files changed

+609
-440
lines changed

app/projects/[slug]/header.tsx

+134-107
Original file line numberDiff line numberDiff line change
@@ -2,119 +2,146 @@
22
import { ArrowLeft, Eye, Github, Twitter, Linkedin } from "lucide-react";
33
import Link from "next/link";
44
import React, { useEffect, useRef, useState } from "react";
5+
import Image from "next/image";
56

67
type Props = {
7-
project: {
8-
url?: string;
9-
title: string;
10-
description: string;
11-
repository?: string;
12-
};
8+
project: {
9+
url?: string;
10+
title: string;
11+
description: string;
12+
repository?: string;
13+
media?: string;
14+
};
1315
};
14-
export const Header: React.FC<Props> = ({ project}) => {
15-
const ref = useRef<HTMLElement>(null);
16-
const [isIntersecting, setIntersecting] = useState(true);
16+
export const Header: React.FC<Props> = ({ project }) => {
17+
const ref = useRef<HTMLElement>(null);
18+
const [isIntersecting, setIntersecting] = useState(true);
1719

18-
const links: { label: string; href: string }[] = [];
19-
if (project.repository) {
20-
links.push({
21-
label: "GitHub",
22-
href: `https://github.com/${project.repository}`,
23-
});
24-
}
25-
if (project.url) {
26-
links.push({
27-
label: "Website",
28-
href: project.url,
29-
});
30-
}
31-
useEffect(() => {
32-
if (!ref.current) return;
33-
const observer = new IntersectionObserver(([entry]) =>
34-
setIntersecting(entry.isIntersecting),
35-
);
20+
const links: { label: string; href: string }[] = [];
21+
if (project.repository) {
22+
links.push({
23+
label: "GitHub",
24+
href: `https://github.com/${project.repository}`,
25+
});
26+
}
27+
if (project.url) {
28+
links.push({
29+
label: "Website",
30+
href: project.url,
31+
});
32+
}
33+
useEffect(() => {
34+
if (!ref.current) return;
35+
const observer = new IntersectionObserver(([entry]) =>
36+
setIntersecting(entry.isIntersecting)
37+
);
3638

37-
observer.observe(ref.current);
38-
return () => observer.disconnect();
39-
}, []);
39+
observer.observe(ref.current);
40+
return () => observer.disconnect();
41+
}, []);
4042

41-
return (
42-
<header
43-
ref={ref}
44-
className="relative isolate overflow-hidden bg-gradient-to-tl from-black via-zinc-900 to-black"
45-
>
46-
<div
47-
className={`fixed inset-x-0 top-0 z-50 backdrop-blur lg:backdrop-blur-none duration-200 border-b lg:bg-transparent ${
48-
isIntersecting
49-
? "bg-zinc-900/0 border-transparent"
50-
: "bg-white/10 border-zinc-200 lg:border-transparent"
51-
}`}
52-
>
53-
<div className="container flex flex-row-reverse items-center justify-between p-6 mx-auto">
54-
<div className="flex justify-between gap-8">
55-
<span
56-
title="View counter for this page"
57-
className={`duration-200 hover:font-medium flex items-center gap-1 ${
58-
isIntersecting
59-
? " text-zinc-400 hover:text-zinc-100"
60-
: "text-zinc-600 hover:text-zinc-900"
61-
} `}
62-
>
63-
</span>
64-
<Link target="_blank" href="https://www.linkedin.com/in/thomas-o-brien-47078b165/?originalSubdomain=au">
65-
<Linkedin
66-
className={`w-6 h-6 duration-200 hover:font-medium ${
67-
isIntersecting
68-
? " text-zinc-400 hover:text-zinc-100"
69-
: "text-zinc-600 hover:text-zinc-900"
70-
} `}
71-
/>
72-
</Link>
73-
<Link target="_blank" href="https://github.com/Tom0Brien">
74-
<Github
75-
className={`w-6 h-6 duration-200 hover:font-medium ${
76-
isIntersecting
77-
? " text-zinc-400 hover:text-zinc-100"
78-
: "text-zinc-600 hover:text-zinc-900"
79-
} `}
80-
/>
81-
</Link>
82-
</div>
43+
return (
44+
<header
45+
ref={ref}
46+
className="relative isolate overflow-hidden bg-gradient-to-tl from-black via-zinc-900 to-black"
47+
>
48+
<div
49+
className={`fixed inset-x-0 top-0 z-50 backdrop-blur lg:backdrop-blur-none duration-200 border-b lg:bg-transparent ${
50+
isIntersecting
51+
? "bg-zinc-900/0 border-transparent"
52+
: "bg-white/10 border-zinc-200 lg:border-transparent"
53+
}`}
54+
>
55+
<div className="container flex flex-row-reverse items-center justify-between p-6 mx-auto">
56+
<div className="flex justify-between gap-8">
57+
<span
58+
title="View counter for this page"
59+
className={`duration-200 hover:font-medium flex items-center gap-1 ${
60+
isIntersecting
61+
? " text-zinc-400 hover:text-zinc-100"
62+
: "text-zinc-600 hover:text-zinc-900"
63+
} `}
64+
></span>
65+
<Link
66+
target="_blank"
67+
href="https://www.linkedin.com/in/thomas-o-brien-47078b165/?originalSubdomain=au"
68+
>
69+
<Linkedin
70+
className={`w-6 h-6 duration-200 hover:font-medium ${
71+
isIntersecting
72+
? " text-zinc-400 hover:text-zinc-100"
73+
: "text-zinc-600 hover:text-zinc-900"
74+
} `}
75+
/>
76+
</Link>
77+
<Link target="_blank" href="https://github.com/Tom0Brien">
78+
<Github
79+
className={`w-6 h-6 duration-200 hover:font-medium ${
80+
isIntersecting
81+
? " text-zinc-400 hover:text-zinc-100"
82+
: "text-zinc-600 hover:text-zinc-900"
83+
} `}
84+
/>
85+
</Link>
86+
</div>
8387

84-
<Link
85-
href="/projects"
86-
className={`duration-200 hover:font-medium ${
87-
isIntersecting
88-
? " text-zinc-400 hover:text-zinc-100"
89-
: "text-zinc-600 hover:text-zinc-900"
90-
} `}
91-
>
92-
<ArrowLeft className="w-6 h-6 " />
93-
</Link>
94-
</div>
95-
</div>
96-
<div className="container mx-auto relative isolate overflow-hidden py-24 sm:py-32">
97-
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-center flex flex-col items-center">
98-
<div className="mx-auto max-w-2xl lg:mx-0">
99-
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-6xl font-display">
100-
{project.title}
101-
</h1>
102-
<p className="mt-6 text-lg leading-8 text-zinc-300">
103-
{project.description}
104-
</p>
105-
</div>
88+
<Link
89+
href="/projects"
90+
className={`duration-200 hover:font-medium ${
91+
isIntersecting
92+
? " text-zinc-400 hover:text-zinc-100"
93+
: "text-zinc-600 hover:text-zinc-900"
94+
} `}
95+
>
96+
<ArrowLeft className="w-6 h-6 " />
97+
</Link>
98+
</div>
99+
</div>
100+
<div className="container mx-auto relative isolate overflow-hidden py-24 sm:py-32">
101+
<div className="mx-auto max-w-7xl px-6 lg:px-8 text-center flex flex-col items-center">
102+
<div className="mx-auto max-w-2xl lg:mx-0">
103+
<h1 className="text-4xl font-bold tracking-tight text-white sm:text-6xl font-display">
104+
{project.title}
105+
</h1>
106+
<p className="mt-6 text-lg leading-8 text-zinc-300">
107+
{project.description}
108+
</p>
109+
</div>
106110

107-
<div className="mx-auto mt-10 max-w-2xl lg:mx-0 lg:max-w-none">
108-
<div className="grid grid-cols-1 gap-y-6 gap-x-8 text-base font-semibold leading-7 text-white sm:grid-cols-2 md:flex lg:gap-x-10">
109-
{links.map((link) => (
110-
<Link target="_blank" key={link.label} href={link.href}>
111-
{link.label} <span aria-hidden="true">&rarr;</span>
112-
</Link>
113-
))}
114-
</div>
115-
</div>
116-
</div>
117-
</div>
118-
</header>
119-
);
111+
{project.media && (
112+
<div className="relative w-full h-96 mt-8 overflow-hidden rounded-lg">
113+
{project.media.endsWith(".mp4") ? (
114+
<video
115+
autoPlay
116+
loop
117+
muted
118+
playsInline
119+
className="object-cover w-full h-full"
120+
>
121+
<source src={project.media} type="video/mp4" />
122+
</video>
123+
) : (
124+
<Image
125+
src={project.media}
126+
alt={project.title}
127+
fill
128+
className="object-cover"
129+
/>
130+
)}
131+
</div>
132+
)}
133+
134+
<div className="mx-auto mt-10 max-w-2xl lg:mx-0 lg:max-w-none">
135+
<div className="grid grid-cols-1 gap-y-6 gap-x-8 text-base font-semibold leading-7 text-white sm:grid-cols-2 md:flex lg:gap-x-10">
136+
{links.map((link) => (
137+
<Link target="_blank" key={link.label} href={link.href}>
138+
{link.label} <span aria-hidden="true">&rarr;</span>
139+
</Link>
140+
))}
141+
</div>
142+
</div>
143+
</div>
144+
</div>
145+
</header>
146+
);
120147
};

app/projects/article.tsx

+86-31
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,93 @@
11
import type { Project } from "@/.contentlayer/generated";
22
import Link from "next/link";
3-
import { Eye, View } from "lucide-react";
3+
import { Eye } from "lucide-react";
4+
import Image from "next/image";
45

56
type Props = {
6-
project: Project;
7+
project: Project;
78
};
89

9-
export const Article: React.FC<Props> = ({ project}) => {
10-
return (
11-
<Link href={`/projects/${project.slug}`}>
12-
<article className="p-4 md:p-8">
13-
<div className="flex justify-between gap-2 items-center">
14-
<span className="text-xs duration-1000 text-zinc-200 group-hover:text-white group-hover:border-zinc-200 drop-shadow-orange">
15-
{project.date ? (
16-
<time dateTime={new Date(project.date).toISOString()}>
17-
{Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(
18-
new Date(project.date),
19-
)}
20-
</time>
21-
) : (
22-
<span>SOON</span>
23-
)}
24-
</span>
25-
<span className="text-zinc-500 text-xs flex items-center gap-1">
26-
<Eye className="w-4 h-4" />{" "}
27-
</span>
28-
</div>
29-
<h2 className="z-20 text-xl font-medium duration-1000 lg:text-3xl text-zinc-200 group-hover:text-white font-display">
30-
{project.title}
31-
</h2>
32-
<p className="z-20 mt-4 text-sm duration-1000 text-zinc-400 group-hover:text-zinc-200">
33-
{project.description}
34-
</p>
35-
</article>
36-
</Link>
37-
);
10+
export const Article: React.FC<Props> = ({ project }) => {
11+
return (
12+
<Link href={`/projects/${project.slug}`}>
13+
<article className="p-4 md:p-8">
14+
<div className="flex justify-between gap-2 items-center">
15+
<span className="text-xs duration-1000 text-zinc-200 group-hover:text-white group-hover:border-zinc-200 drop-shadow-orange">
16+
{project.date ? (
17+
<time dateTime={new Date(project.date).toISOString()}>
18+
{Intl.DateTimeFormat(undefined, { dateStyle: "medium" }).format(
19+
new Date(project.date)
20+
)}
21+
</time>
22+
) : (
23+
<span>SOON</span>
24+
)}
25+
</span>
26+
<span className="text-zinc-500 text-xs flex items-center gap-1">
27+
<Eye className="w-4 h-4" />
28+
</span>
29+
</div>
30+
31+
<h2 className="z-20 text-xl font-medium duration-1000 lg:text-3xl text-zinc-200 group-hover:text-white font-display mt-4">
32+
{project.title}
33+
</h2>
34+
{project.media && (
35+
<div className="relative w-full h-48 mt-4 overflow-hidden rounded-lg">
36+
{project.media.endsWith(".mp4") ? (
37+
<div className="relative w-full h-full">
38+
<video
39+
autoPlay
40+
loop
41+
muted
42+
playsInline
43+
className="absolute top-0 left-0 w-full h-full object-contain bg-black"
44+
>
45+
<source src={project.media} type="video/mp4" />
46+
</video>
47+
</div>
48+
) : (
49+
<Image
50+
src={project.media}
51+
alt={project.title}
52+
fill
53+
className="object-cover"
54+
/>
55+
)}
56+
</div>
57+
)}
58+
<p className="z-20 mt-4 text-sm duration-1000 text-zinc-400 group-hover:text-zinc-200">
59+
{project.description}
60+
</p>
61+
62+
{project.repository && (
63+
<div className="flex items-center gap-2 mt-4">
64+
<span className="text-zinc-400 text-sm">
65+
<a
66+
href={`https://github.com/${project.repository}`}
67+
target="_blank"
68+
rel="noopener noreferrer"
69+
className="hover:text-zinc-200 duration-200"
70+
>
71+
<svg
72+
xmlns="http://www.w3.org/2000/svg"
73+
width="16"
74+
height="16"
75+
viewBox="0 0 24 24"
76+
fill="none"
77+
stroke="currentColor"
78+
strokeWidth="2"
79+
strokeLinecap="round"
80+
strokeLinejoin="round"
81+
className="inline-block ml-1 mb-1"
82+
>
83+
<path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" />
84+
</svg>{" "}
85+
Github
86+
</a>
87+
</span>
88+
</div>
89+
)}
90+
</article>
91+
</Link>
92+
);
3893
};

0 commit comments

Comments
 (0)