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
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 129 KiB |
@@ -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’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’m a developer based in the{" "}
|
||||
<Link
|
||||
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&fs=1&showinfo=1&rel=0&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;
|
||||
|
Before Width: | Height: | Size: 128 KiB |
@@ -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;
|
||||
|
Before Width: | Height: | Size: 128 KiB |
@@ -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)];
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 283 KiB |
@@ -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>
|
||||
. © 2016.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
|
||||
export default Page;
|
||||
|
Before Width: | Height: | Size: 283 KiB |
@@ -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 />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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’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’m a frontend web developer based in the{" "}
|
||||
<Link
|
||||
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&fs=1&showinfo=1&rel=0&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’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;
|
||||
@@ -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™](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/"
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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})`,
|
||||
}}
|
||||
|
||||