1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2026-06-05 19:15:30 -04:00

refactor: redesign home page, remove old video pages, migrate to @tailwindcss/typography

- Replace root page with new (home) route featuring a project showcase (Domainstack, Sofa, Versioneer, Snoozle)
- Remove /birthday and /hillary video pages
- Replace custom hand-rolled .prose styles with @tailwindcss/typography via a .markdown utility class
- Remove heading-anchor component and .nvmrc
This commit is contained in:
2026-04-08 15:00:49 -04:00
parent 22ea1aa0bc
commit 5a3c7b9613
29 changed files with 348 additions and 601 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

+104
View File
@@ -0,0 +1,104 @@
import { ArrowUpRight } from "lucide-react";
import Image, { type StaticImageData } from "next/image";
import Link from "next/link";
import domainstackIcon from "./icons/domainstack.png";
import snoozleIcon from "./icons/snoozle.png";
import sofaIcon from "./icons/sofa.png";
import versioneerIcon from "./icons/versioneer.png";
type Project = {
name: string;
url: string;
tagline: string;
icon: StaticImageData;
};
const projects: readonly Project[] = [
{
name: "Domainstack",
url: "https://domainstack.io",
tagline: "Domain intelligence made easy",
icon: domainstackIcon,
},
{
name: "Sofa",
url: "https://sofa.watch",
tagline: "Self-hosted movie & TV show tracker",
icon: sofaIcon,
},
{
name: "Versioneer",
url: "https://versioneer.app",
tagline: "macOS app updater with privacy-friendly crowdsourcing",
icon: versioneerIcon,
},
{
name: "Snoozle",
url: "https://snoozle.ai",
tagline: "AI-powered bedtime stories for kids",
icon: snoozleIcon,
},
] as const;
const Page = () => (
<>
<h1 className="text-lg font-medium">
Hi there! I&rsquo;m Jake.{" "}
<span className="motion-safe:animate-wave ml-0.5 inline-block origin-[65%_80%] text-[1.2rem]">
👋
</span>
</h1>
<div className="markdown">
<p className="text-sm leading-normal">
I&rsquo;m a developer based in the{" "}
<Link
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&amp;fs=1&amp;showinfo=1&amp;rel=0&amp;iv_load_policy=3"
title='"Boston Accent Trailer - Late Night with Seth Meyers" on YouTube'
>
Boston
</Link>{" "}
area working on some cool stuff:
</p>
</div>
<section className="my-3">
<ul className="flex flex-col gap-2 sm:gap-3">
{projects.map((project) => (
<li key={project.name}>
<a
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="group flex flex-col gap-1 rounded-md py-1 transition-colors sm:flex-row sm:items-center sm:gap-4"
>
<div className="flex items-center gap-3">
<Image
src={project.icon}
alt={project.name}
width={64}
height={64}
decoding="async"
className="ring-border size-6 shrink-0 rounded-[26%] ring-1"
/>
<span className="text-primary text-sm font-medium group-hover:underline group-hover:underline-offset-4">
{project.name}
</span>
</div>
<span className="text-muted-foreground ml-9 text-xs text-pretty sm:ml-auto">
{project.tagline}
<ArrowUpRight
className="group-hover:text-primary ml-1 inline size-3.5 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
aria-hidden="true"
/>
</span>
</a>
</li>
))}
</ul>
</section>
</>
);
export default Page;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

-53
View File
@@ -1,53 +0,0 @@
import { JsonLd } from "react-schemaorg";
import type { VideoObject } from "schema-dts";
import { PageTitle } from "@/components/layout/page-title";
import { Video } from "@/components/video";
import { createMetadata } from "@/lib/metadata";
import thumbnail from "./thumbnail.png";
export const metadata = createMetadata({
title: "🎉 Cranky Birthday Boy on VHS Tape 📼",
description: "The origin of my hatred for the Happy Birthday song.",
canonical: "/birthday",
openGraph: {
videos: [
{
url: "https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/birthday-pavk1LBK4H6xF8ZWeR0oTcaabGuQ8T.webm",
type: "video/webm",
},
],
},
});
const Page = () => (
<>
<JsonLd<VideoObject>
item={{
"@context": "https://schema.org",
"@type": "VideoObject",
name: metadata.title as string,
description: metadata.description as string,
contentUrl:
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/birthday-pavk1LBK4H6xF8ZWeR0oTcaabGuQ8T.webm",
thumbnailUrl: `${process.env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
embedUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/birthday`,
uploadDate: "1996-02-06T00:00:00Z",
duration: "PT6M10S",
}}
/>
<PageTitle canonical="/birthday">1996.mov</PageTitle>
<Video
src={[
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/birthday-pavk1LBK4H6xF8ZWeR0oTcaabGuQ8T.webm",
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/birthday-EkbYbrKY8reheQ4UPcP22ipzpMZ2MC.mp4",
]}
poster={thumbnail.src}
/>
</>
);
export default Page;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

+7 -153
View File
@@ -2,6 +2,7 @@
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
@plugin "@tailwindcss/typography";
@custom-variant dark (&:where(.dark *));
@@ -201,159 +202,7 @@
cursor: pointer;
}
.prose {
@apply text-foreground/90 max-w-none text-sm leading-7;
/* Headings */
h1,
h2,
h3,
h4,
h5,
h6 {
@apply text-primary scroll-m-20 font-semibold tracking-tight;
}
h1 {
@apply mt-8 mb-4 text-2xl leading-tight;
}
h2 {
@apply mt-8 mb-4 border-b pb-2 text-xl leading-tight;
}
h3 {
@apply mt-6 mb-3 text-lg leading-snug;
}
h4 {
@apply mt-6 mb-2 text-base leading-snug;
}
h5 {
@apply mt-4 mb-2 text-base leading-snug;
}
h6 {
@apply mt-4 mb-2 text-sm leading-snug;
}
/* Text blocks */
p {
@apply text-foreground/90 my-4;
}
strong {
@apply text-primary font-semibold;
}
em {
@apply italic;
}
/* Separators */
hr {
@apply my-8 border-0 border-t;
border-color: var(--border);
}
/* Links */
a {
@apply text-primary font-medium underline underline-offset-4 transition-colors;
}
a:hover {
@apply opacity-80;
}
/* Lists */
ul {
@apply my-4 ml-6 list-disc;
}
ol {
@apply my-4 ml-6 list-decimal;
}
li {
@apply text-foreground/80;
}
li + li {
@apply mt-1;
}
li > ul,
li > ol {
@apply my-2;
}
/* Blockquotes */
blockquote {
@apply my-6 border-l-4 pl-4 italic;
border-color: var(--border);
}
blockquote p {
@apply text-foreground/75;
}
blockquote *::before,
blockquote *::after {
content: none;
}
/* Inline code */
:not(pre) > code {
@apply bg-muted text-foreground rounded-sm px-1 py-0.5 text-[0.9em];
}
:not(pre) > code::before,
:not(pre) > code::after {
content: none;
}
/* Code blocks */
pre {
@apply bg-muted my-6 overflow-x-auto rounded-lg p-4 text-sm leading-6;
}
pre code {
@apply bg-transparent p-0 text-inherit;
}
pre code::before,
pre code::after {
content: none;
}
/* Tables */
table {
@apply my-6 w-full border-collapse text-sm;
}
thead {
@apply border-b;
}
tr {
@apply border-b;
}
th {
@apply text-primary px-3 py-2 text-left align-middle font-semibold;
}
td {
@apply text-foreground/90 px-3 py-2 align-middle;
}
/* Images / media */
img {
@apply my-6 rounded-lg border;
}
video {
@apply my-6 rounded-lg;
}
/* Misc */
kbd {
@apply bg-muted rounded border px-1.5 py-0.5 text-xs;
}
mark {
@apply bg-muted rounded px-1;
}
/* First/last child spacing cleanup */
> :first-child {
@apply mt-0;
}
> :last-child {
@apply mb-0;
}
}
}
/* View Transitions - uses tw-animate-css's `enter` and `exit` keyframes */
@layer base {
/* View Transitions - uses tw-animate-css's `enter` and `exit` keyframes */
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
@@ -383,3 +232,8 @@
animation: 200ms ease-out forwards enter;
}
}
/* Prose */
.markdown {
@apply prose prose-neutral dark:prose-invert prose-sm prose-hr:border-foreground/20 prose-headings:font-semibold prose-headings:text-primary prose-headings:tracking-tight prose-p:text-foreground/90 prose-strong:text-primary prose-li:text-foreground/80 prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-blockquote:[&_p]:text-foreground/75 prose-blockquote:*:before:content-none prose-blockquote:*:after:content-none prose-code:bg-muted prose-code:text-foreground prose-code:px-1 prose-code:py-0.5 prose-code:rounded-sm prose-code:text-[0.9em] prose-code:before:content-none prose-code:after:content-none max-w-none [&_table]:!border-[color:var(--border)] [&_td]:!border-[color:var(--border)] [&_th]:!border-[color:var(--border)];
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

-86
View File
@@ -1,86 +0,0 @@
import { JsonLd } from "react-schemaorg";
import type { VideoObject } from "schema-dts";
import { PageTitle } from "@/components/layout/page-title";
import { Video } from "@/components/video";
import { createMetadata } from "@/lib/metadata";
import thumbnail from "./thumbnail.png";
export const metadata = createMetadata({
title: "My Brief Apperance in Hillary Clinton's DNC Video",
description:
"My brief apperance in one of Hillary Clinton's 2016 DNC convention videos on substance abuse.",
canonical: "/hillary",
openGraph: {
videos: [
{
url: "https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention-ZTUBLwMcmOE8EJ4tNAhpCli4NAHKcG.webm",
type: "video/webm",
},
],
},
});
const Page = () => (
<>
<JsonLd<VideoObject>
item={{
"@context": "https://schema.org",
"@type": "VideoObject",
name: metadata.title as string,
description: metadata.description as string,
contentUrl:
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention-ZTUBLwMcmOE8EJ4tNAhpCli4NAHKcG.webm",
thumbnailUrl: `${process.env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
embedUrl: `${process.env.NEXT_PUBLIC_BASE_URL}/hillary`,
uploadDate: "2016-07-25T00:00:00Z",
duration: "PT1M51S",
}}
/>
<PageTitle canonical="/hillary">HRC.mov</PageTitle>
<Video
src={[
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention-ZTUBLwMcmOE8EJ4tNAhpCli4NAHKcG.webm",
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention-T6klrrArGL0IO4QPaloIiIH164UqUC.mp4",
"https://ijyxfbpcm3itvdly.public.blob.vercel-storage.com/convention.en-uHnecgVCrT9xA8EkzdEaeIwB0rHFC9.vtt",
]}
poster={thumbnail.src}
/>
<p className="text-muted-foreground mx-4 mt-5 mb-0 text-center text-sm leading-relaxed">
Video is property of{" "}
<a
href="https://www.hillaryclinton.com/"
target="_blank"
rel="noopener noreferrer"
className="font-bold"
>
Hillary for America
</a>
, the{" "}
<a
href="https://democrats.org/"
target="_blank"
rel="noopener noreferrer"
className="font-bold"
>
Democratic National Committee
</a>
, and{" "}
<a
href="https://cnnpressroom.blogs.cnn.com/"
target="_blank"
rel="noopener noreferrer"
className="font-bold"
>
CNN / WarnerMedia
</a>
. &copy; 2016.
</p>
</>
);
export default Page;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

+7 -5
View File
@@ -63,11 +63,13 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
<body className="bg-background text-foreground font-sans antialiased">
<Providers>
<Header />
<main className="mx-auto mt-4 w-full max-w-[720px] px-5">
<ViewTransition>{children}</ViewTransition>
</main>
<Footer />
<div className="mx-auto w-full max-w-[720px] px-5">
<Header />
<main className="mt-4 w-full">
<ViewTransition>{children}</ViewTransition>
</main>
<Footer />
</div>
<Toaster position="bottom-center" hotkey={[]} />
</Providers>
<Analytics />
+3 -1
View File
@@ -179,7 +179,9 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
/>
</h1>
<MDXContent />
<article className="markdown">
<MDXContent />
</article>
<section id="comments" className="isolate my-8 w-full border-t-2 pt-8">
<div className="mx-auto w-full max-w-3xl space-y-6">
-48
View File
@@ -1,48 +0,0 @@
import { LockIcon } from "lucide-react";
import Link from "next/link";
const Page = () => (
<div className="prose">
<h1 className="text-2xl font-medium">
Hi there! I&rsquo;m Jake.{" "}
<span className="motion-safe:animate-wave ml-0.5 inline-block origin-[65%_80%] text-2xl">
👋
</span>
</h1>
<h2 className="font-normal">
I&rsquo;m a frontend web developer based in the{" "}
<Link
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&amp;fs=1&amp;showinfo=1&amp;rel=0&amp;iv_load_policy=3"
title='"Boston Accent Trailer - Late Night with Seth Meyers" on YouTube'
>
Boston
</Link>{" "}
area.
</h2>
<p className="mt-2 mb-0 text-sm leading-normal">
I&rsquo;m always available to connect over{" "}
<a href="mailto:jake@jarv.is" title="Send an email">
email
</a>{" "}
<sup className="">
<a
href="https://keyoxide.org/hkp/3bc6e5776bf379d36f6714802b0c9cf251e69a39"
target="_blank"
rel="noopener pgpkey"
title="Download my PGP key"
className="not-prose text-muted-foreground hover:text-primary space-x-1 px-0.5 text-nowrap no-underline hover:no-underline"
>
<LockIcon className="inline size-2.5" aria-hidden="true" />
<code className="text-[9px] leading-none tracking-wider text-wrap [word-spacing:-3px]">
2B0C 9CF2 51E6 9A39
</code>
</a>
</sup>
.
</p>
</div>
);
export default Page;
+3 -2
View File
@@ -13,7 +13,7 @@ export const metadata = createMetadata({
export const WarningMarquee = () => (
<Marquee>
<span className="leading-none">
<span className="leading-none text-foreground">
🚨 Trigger warning: excessive marquees, animated GIFs, Comic Sans, popups,{" "}
<code className="text-[0.9rem] font-normal">
color: <span className="text-[#32cd32]">limegreen</span>
@@ -36,7 +36,8 @@ _Previously on the [Cringey Chronicles&trade;](https://web.archive.org/web/20010
<WarningMarquee />
[<Win95Icon className="inline size-4 align-text-top" /> Click here for the _full_ experience (at your own risk).](https://y2k.pages.dev)
<Win95Icon className="inline size-4 align-text-top" /> [Click here for the _full_ experience (at
your own risk).](https://y2k.pages.dev)
<iframe
src="https://jakejarvis.github.io/my-first-website/"
+3 -3
View File
@@ -78,18 +78,18 @@ const Page = async () => {
href={repo?.url}
target="_blank"
rel="noopener noreferrer"
className="inline-block text-base leading-relaxed font-semibold text-[#0969da] hover:underline dark:text-[#76affa]"
className="inline-block text-[15px] font-semibold text-[#0969da] hover:underline dark:text-[#76affa]"
>
{repo?.name}
</a>
{repo?.description && (
<p className="text-foreground/85 text-[13px] leading-relaxed">
<p className="text-foreground/85 text-xs leading-relaxed text-pretty">
{repo?.description}
</p>
)}
<div className="flex flex-wrap gap-x-4 text-xs leading-loose whitespace-nowrap">
<div className="flex flex-wrap gap-x-4 text-[11px] leading-loose whitespace-nowrap">
{repo?.primaryLanguage && (
<div className="text-muted-foreground inline-flex flex-nowrap items-center gap-1.5">
{repo?.primaryLanguage.color && (
+1 -1
View File
@@ -11,7 +11,7 @@ export const metadata = createMetadata({
export const Terminal = () => (
<div
className="relative mx-auto my-6 w-full rounded-lg bg-center bg-no-repeat"
className="not-prose relative mx-auto my-6 w-full rounded-lg bg-center bg-no-repeat"
style={{
backgroundImage: `url(${backgroundImg.src})`,
}}