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

refactor: migrate from Biome to oxlint/oxfmt, remove contact form

- Replace Biome with oxlint + oxfmt (OXC toolchain) for linting and formatting
- Add .oxlintrc.json and .oxfmtrc.json configuration files
- Update VS Code settings and devcontainer to use oxc-vscode extension
- Remove contact form, Resend email integration, and related server action/schema
- Remove unused UI components (accordion, alert, card, tabs, toggle, etc.)
This commit is contained in:
2026-04-05 19:45:18 -04:00
parent b857ab2754
commit 5a1636baa3
114 changed files with 4901 additions and 5258 deletions
+1
View File
@@ -1,4 +1,5 @@
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";
export const { POST, GET } = toNextJsHandler(auth);
+3 -2
View File
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { getAllViewCounts } from "@/lib/server/views";
export const GET = async (): Promise<
@@ -18,9 +19,9 @@ export const GET = async (): Promise<
const total = {
hits: Object.values(views).reduce((acc, curr) => acc + curr, 0),
};
const pages = Object.entries(views).map(([slug, views]) => ({
const pages = Object.entries(views).map(([slug, count]) => ({
slug,
hits: views,
hits: count,
}));
pages.sort((a, b) => b.hits - a.hits);
+1 -1
View File
@@ -1,8 +1,8 @@
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";
-59
View File
@@ -1,59 +0,0 @@
import { ContactForm } from "@/components/contact-form";
import { PageTitle } from "@/components/layout/page-title";
import { createMetadata } from "@/lib/metadata";
export const metadata = createMetadata({
title: "Contact Me",
description:
"Fill out this quick form and I'll get back to you as soon as I can.",
canonical: "/contact",
});
const Page = () => (
<>
<PageTitle canonical="/contact">Contact</PageTitle>
<div className="mx-auto w-full max-w-2xl">
<div className="prose prose-sm prose-neutral dark:prose-invert max-w-none">
<p>
Fill out this quick form and I&rsquo;ll get back to you as soon as I
can! You can also <a href="mailto:jake@jarv.is">email me directly</a>{" "}
or send me a direct message on{" "}
<a
href="https://bsky.app/profile/jarv.is"
target="_blank"
rel="noopener noreferrer"
>
Bluesky
</a>{" "}
or{" "}
<a
href="https://fediverse.jarv.is/@jake"
target="_blank"
rel="noopener noreferrer"
>
Mastodon
</a>
.
</p>
<p>
You can grab my public key here:{" "}
<a
href="https://keyoxide.org/hkp/3bc6e5776bf379d36f6714802b0c9cf251e69a39"
target="_blank"
rel="noopener"
title="3BC6 E577 6BF3 79D3 6F67 1480 2B0C 9CF2 51E6 9A39"
className="relative rounded-sm bg-muted px-[0.3rem] py-[0.2rem] font-medium font-mono text-sm tracking-wider [word-spacing:-0.25em]"
>
2B0C 9CF2 51E6 9A39
</a>
.
</p>
</div>
<ContactForm />
</div>
</>
);
export default Page;
+1
View File
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { buildFeed } from "@/lib/build-feed";
export const GET = async () => {
+1
View File
@@ -1,4 +1,5 @@
import { NextResponse } from "next/server";
import { buildFeed } from "@/lib/build-feed";
export const GET = async () => {
+167 -15
View File
@@ -1,6 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@plugin "@tailwindcss/typography";
@import "shadcn/tailwind.css";
@import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
@custom-variant dark (&:where(.dark *));
@@ -61,6 +61,8 @@
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
@@ -78,11 +80,11 @@
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
@@ -99,12 +101,10 @@
--code-number: oklch(0.56 0 0);
--selection: oklch(0.145 0 0);
--selection-foreground: oklch(1 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--background: oklch(0 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
@@ -123,11 +123,11 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
@@ -185,19 +185,171 @@
@layer base {
* {
@apply border-border outline-ring/50;
@apply outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
::selection {
@apply bg-selection text-selection-foreground;
}
/* https://ui.shadcn.com/docs/components/button#cursor */
button:not(:disabled),
[role="button"]:not(:disabled) {
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 */
+2 -2
View File
@@ -1,8 +1,8 @@
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";
@@ -50,7 +50,7 @@ const Page = () => (
poster={thumbnail.src}
/>
<p className="mx-4 mt-5 mb-0 text-center text-muted-foreground text-sm leading-relaxed">
<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/"
+9 -13
View File
@@ -1,6 +1,7 @@
import { ViewTransition } from "react";
import { JsonLd } from "react-schemaorg";
import type { Person, WebSite } from "schema-dts";
import { Analytics } from "@/app/analytics";
import { Footer } from "@/components/layout/footer";
import { Header } from "@/components/layout/header";
@@ -8,18 +9,18 @@ import { Providers } from "@/components/providers";
import { Toaster } from "@/components/ui/sonner";
import authorConfig from "@/lib/config/author";
import siteConfig from "@/lib/config/site";
import { Inter, JetBrainsMono } from "@/lib/fonts";
import { defaultMetadata } from "@/lib/metadata";
import "./globals.css";
import { defaultMetadata } from "@/lib/metadata";
import { cn } from "@/lib/utils";
export const metadata = defaultMetadata;
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
<html
lang={process.env.NEXT_PUBLIC_SITE_LOCALE}
className={`${Inter.variable} ${JetBrainsMono.variable}`}
className={cn(Inter.variable, JetBrainsMono.variable)}
suppressHydrationWarning
>
<head>
@@ -29,11 +30,9 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
"@type": "Person",
"@id": `${process.env.NEXT_PUBLIC_BASE_URL}/#person`,
name: authorConfig.name,
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
url: process.env.NEXT_PUBLIC_BASE_URL!,
image: [`${process.env.NEXT_PUBLIC_BASE_URL}/opengraph-image.jpg`],
sameAs: [
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
process.env.NEXT_PUBLIC_BASE_URL!,
`https://${authorConfig.social?.mastodon}`,
`https://github.com/${authorConfig.social?.github}`,
@@ -62,19 +61,16 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
/>
</head>
<body className="bg-background font-sans text-foreground antialiased">
<body className="bg-background text-foreground font-sans antialiased">
<Providers>
<Header />
<div className="mx-auto mt-4 w-full max-w-4xl px-5">
<main>
<ViewTransition>{children}</ViewTransition>
</main>
</div>
<main className="mx-auto mt-4 w-full max-w-[720px] px-5">
<ViewTransition>{children}</ViewTransition>
</main>
<Footer />
<Toaster position="bottom-center" hotkey={[]} />
<Analytics />
</Providers>
<Analytics />
</body>
</html>
);
+3 -8
View File
@@ -1,8 +1,8 @@
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";
@@ -49,7 +49,7 @@ const Page = () => (
poster={thumbnail.src}
/>
<p className="mx-4 mt-5 mb-0 text-center text-muted-foreground text-sm leading-relaxed">
<p className="text-muted-foreground mx-4 mt-5 mb-0 text-center text-sm leading-relaxed">
Video is property of{" "}
<a
href="https://web.archive.org/web/20070511004304/www.g4techtv.ca"
@@ -60,12 +60,7 @@ const Page = () => (
G4techTV Canada
</a>{" "}
&amp;{" "}
<a
href="https://leo.fm/"
target="_blank"
rel="noopener noreferrer"
className="font-bold"
>
<a href="https://leo.fm/" target="_blank" rel="noopener noreferrer" className="font-bold">
Leo Laporte
</a>
. &copy; 2007 G4 Media, Inc.
+1
View File
@@ -1,4 +1,5 @@
import type { MetadataRoute } from "next";
import siteConfig from "@/lib/config/site";
const manifest = (): MetadataRoute.Manifest => {
+9 -5
View File
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Video } from "@/components/video";
@@ -21,12 +22,15 @@ const Page = () => (
/>
<div className="mt-6 text-center">
<h1 className="my-2 font-semibold text-2xl md:text-3xl">
Page Not Found
</h1>
<h1 className="my-2 text-2xl font-semibold md:text-3xl">Page Not Found</h1>
<Button className="mt-4 mb-0 text-[15px] leading-none" size="lg" asChild>
<Link href="/">Go home?</Link>
<Button
className="mt-4 mb-0 text-[15px] leading-none"
size="lg"
nativeButton={false}
render={<Link href="/" />}
>
Go home?
</Button>
</div>
</>
+12 -23
View File
@@ -1,9 +1,10 @@
import fs from "node:fs";
import path from "node:path";
import { notFound } from "next/navigation";
import { ImageResponse } from "next/og";
import siteConfig from "@/lib/config/site";
import siteConfig from "@/lib/config/site";
import { loadGoogleFont } from "@/lib/og-utils";
import { getFrontMatter, getSlugs, POSTS_DIR } from "@/lib/posts";
@@ -52,11 +53,7 @@ const getLocalImage = async (src: string): Promise<ArrayBuffer | string> => {
}
};
const OpenGraphImage = async ({
params,
}: {
params: Promise<{ slug: string }>;
}) => {
const OpenGraphImage = async ({ params }: { params: Promise<{ slug: string }> }) => {
try {
const { slug } = await params;
@@ -66,9 +63,7 @@ const OpenGraphImage = async ({
// IMPORTANT: include these exact paths in next.config.ts under "outputFileTracingIncludes"
const [postImg, avatarImg] = await Promise.all([
frontmatter.image
? getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter.image}`)
: null,
frontmatter.image ? getLocalImage(`${POSTS_DIR}/${slug}/${frontmatter.image}`) : null,
getLocalImage("app/avatar.jpg"),
]);
@@ -139,7 +134,7 @@ const OpenGraphImage = async ({
}}
>
{avatarImg && (
// biome-ignore lint/performance/noImgElement: Satori/ImageResponse requires raw <img> tags
// oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags
<img
// @ts-expect-error
src={avatarImg}
@@ -213,14 +208,11 @@ const OpenGraphImage = async ({
lineHeight: "1.2",
}}
>
{new Date(frontmatter.date).toLocaleDateString(
process.env.NEXT_PUBLIC_SITE_LOCALE,
{
year: "numeric",
month: "long",
day: "numeric",
},
)}
{new Date(frontmatter.date).toLocaleDateString(process.env.NEXT_PUBLIC_SITE_LOCALE, {
year: "numeric",
month: "long",
day: "numeric",
})}
</div>
</div>
@@ -232,7 +224,7 @@ const OpenGraphImage = async ({
flexGrow: 0,
}}
>
{/* biome-ignore lint/performance/noImgElement: Satori/ImageResponse requires raw <img> tags */}
{/* oxlint-disable-next-line nextjs/no-img-element - Satori/ImageResponse requires raw <img> tags */}
<img
// @ts-expect-error
src={postImg}
@@ -266,10 +258,7 @@ const OpenGraphImage = async ({
},
);
} catch (error) {
console.error(
"[/notes/[slug]/opengraph-image] error generating open graph image:",
error,
);
console.error("[/notes/[slug]/opengraph-image] error generating open graph image:", error);
notFound();
}
};
+14 -27
View File
@@ -11,15 +11,16 @@ import { notFound } from "next/navigation";
import { Suspense } from "react";
import { JsonLd } from "react-schemaorg";
import type { BlogPosting } from "schema-dts";
import { CommentCount } from "@/components/comment-count";
import { Comments } from "@/components/comments/comments";
import { CommentsSkeleton } from "@/components/comments/comments-skeleton";
import { ViewCounter } from "@/components/view-counter";
import authorConfig from "@/lib/config/author";
import siteConfig from "@/lib/config/site";
import { createMetadata } from "@/lib/metadata";
import { getFrontMatter, getSlugs, POSTS_DIR } from "@/lib/posts";
import { size as ogImageSize } from "./opengraph-image";
export const generateStaticParams = async () => {
@@ -79,9 +80,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
}),
};
const { default: MDXContent } = await import(
`../../../${POSTS_DIR}/${slug}/index.mdx`
);
const { default: MDXContent } = await import(`../../../${POSTS_DIR}/${slug}/index.mdx`);
return (
<>
@@ -110,17 +109,14 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
}}
/>
<div className="flex flex-wrap justify-items-start gap-4 text-[13px] text-foreground/70 tracking-wide">
<div className="text-foreground/70 flex flex-wrap justify-items-start gap-4 text-[13px] tracking-wide">
<Link
href={`/${POSTS_DIR}/${frontmatter?.slug}`}
className={
"flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-foreground/70 hover:no-underline"
"text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
}
>
<CalendarDaysIcon
className="inline size-3 shrink-0"
aria-hidden="true"
/>
<CalendarDaysIcon className="inline size-3 shrink-0" aria-hidden="true" />
<time
dateTime={formattedDates.dateISO}
title={formattedDates.dateTitle}
@@ -137,7 +133,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
<span
key={tag}
title={tag}
className="mx-px lowercase before:pr-0.5 before:text-foreground/40 before:content-['\0023'] first-of-type:ml-0 last-of-type:mr-0"
className="before:text-foreground/40 mx-px lowercase before:pr-0.5 before:content-['#'] first-of-type:ml-0 last-of-type:mr-0"
>
{tag}
</span>
@@ -149,24 +145,18 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter?.slug}/index.mdx`}
title={`Edit "${frontmatter?.title}" on GitHub`}
className={
"flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-foreground/70 hover:no-underline"
"text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
}
>
<SquarePenIcon
className="inline size-3 shrink-0"
aria-hidden="true"
/>
<SquarePenIcon className="inline size-3 shrink-0" aria-hidden="true" />
<span>Improve This Post</span>
</Link>
<Link
href={`/${POSTS_DIR}/${frontmatter?.slug}#comments`}
className="flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-foreground/70 hover:no-underline"
className="text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
>
<MessagesSquareIcon
className="inline size-3 shrink-0"
aria-hidden="true"
/>
<MessagesSquareIcon className="inline size-3 shrink-0" aria-hidden="true" />
<CommentCount slug={`${POSTS_DIR}/${frontmatter?.slug}`} />
</Link>
@@ -177,12 +167,11 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
</div>
<h1
className="my-5 font-medium text-3xl tracking-tight"
className="my-5 text-3xl font-medium tracking-tight"
style={{ viewTransitionName: `note-title-${frontmatter?.slug}` }}
>
<Link
href={`/${POSTS_DIR}/${frontmatter?.slug}`}
// biome-ignore lint/security/noDangerouslySetInnerHtml: htmlTitle is sanitized by rehypeSanitize in lib/posts.ts
dangerouslySetInnerHTML={{
__html: frontmatter.htmlTitle || frontmatter.title,
}}
@@ -195,10 +184,8 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
<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">
{frontmatter?.noComments ? (
<div className="flex justify-center rounded-lg bg-muted/40 px-6 py-12">
<p className="text-center font-medium text-lg">
Comments are closed.
</p>
<div className="bg-muted/40 flex justify-center rounded-lg px-6 py-12">
<p className="text-center text-lg font-medium">Comments are closed.</p>
</div>
) : (
<Suspense fallback={<CommentsSkeleton />}>
+24 -34
View File
@@ -1,4 +1,5 @@
import Link from "next/link";
import { PageTitle } from "@/components/layout/page-title";
import { PostStats, PostStatsProvider } from "@/components/post-stats";
import authorConfig from "@/lib/config/author";
@@ -16,8 +17,7 @@ const PostsList = async () => {
const formattedPosts = posts.map((post) => {
const d = new Date(post.date);
return {
...post,
return Object.assign(post, {
year: d.getUTCFullYear(),
dateISO: d.toISOString(),
dateTitle: d.toLocaleString("en-US", {
@@ -32,7 +32,7 @@ const PostsList = async () => {
month: "short",
day: "numeric",
}),
};
});
});
const postsByYear: {
@@ -53,49 +53,39 @@ const PostsList = async () => {
const sections: React.ReactNode[] = [];
Object.entries(postsByYear).forEach(([year, posts]) => {
Object.entries(postsByYear).forEach(([year, yearPosts]) => {
sections.push(
<section className="my-8 first-of-type:mt-0 last-of-type:mb-0" key={year}>
<h2
id={year}
className="mt-0 mb-4 font-semibold text-2xl tracking-tight"
>
<h2 id={year} className="mt-0 mb-4 text-2xl font-semibold tracking-tight">
{year}
</h2>
<ul className="space-y-4">
{posts.map(
({ slug, dateISO, dateTitle, dateDisplay, title, htmlTitle }) => (
<li className="flex text-base leading-relaxed" key={slug}>
<span className="w-18 shrink-0 text-muted-foreground md:w-22">
<time
dateTime={dateISO}
title={dateTitle}
suppressHydrationWarning
>
{dateDisplay}
</time>
</span>
<div className="space-x-2">
<Link
href={`/${POSTS_DIR}/${slug}`}
// biome-ignore lint/security/noDangerouslySetInnerHtml: htmlTitle is sanitized by rehypeSanitize in lib/posts.ts
dangerouslySetInnerHTML={{ __html: htmlTitle || title }}
className="mr-2.5 underline-offset-4 hover:underline"
style={{ viewTransitionName: `note-title-${slug}` }}
/>
{yearPosts.map(({ slug, dateISO, dateTitle, dateDisplay, title, htmlTitle }) => (
<li className="flex text-base leading-relaxed" key={slug}>
<span className="text-muted-foreground w-18 shrink-0 md:w-22">
<time dateTime={dateISO} title={dateTitle} suppressHydrationWarning>
{dateDisplay}
</time>
</span>
<div className="space-x-2">
<Link
href={`/${POSTS_DIR}/${slug}`}
dangerouslySetInnerHTML={{ __html: htmlTitle || title }}
className="mr-2.5 underline-offset-4 hover:underline"
style={{ viewTransitionName: `note-title-${slug}` }}
/>
<PostStats slug={`${POSTS_DIR}/${slug}`} />
</div>
</li>
),
)}
<PostStats slug={`${POSTS_DIR}/${slug}`} />
</div>
</li>
))}
</ul>
</section>,
);
});
// grouped posts enter this component ordered chronologically -- we want reverse chronological
return <>{sections.reverse()}</>;
return <>{sections.toReversed()}</>;
};
const Page = async () => (
+10 -119
View File
@@ -1,21 +1,11 @@
import { LockIcon } from "lucide-react";
import Link from "next/link";
import { cn } from "@/lib/utils";
const Page = () => (
<div
className={cn(
"prose prose-neutral dark:prose-invert prose-sm max-w-none",
"prose-headings:mt-0 prose-headings:mb-3 prose-headings:font-semibold prose-headings:text-primary prose-headings:tracking-tight",
"prose-p:my-3 prose-li:text-foreground/80 prose-p:text-foreground/90 prose-strong:text-primary prose-p:leading-[1.75] md:prose-p:leading-relaxed",
"prose-a:font-medium prose-a:text-primary prose-a:underline prose-a:underline-offset-4",
"prose-code:rounded-sm prose-code:bg-muted prose-code:px-1 prose-code:py-0.5 prose-code:text-[0.9em] prose-code:text-foreground prose-code:before:content-none prose-code:after:content-none",
"[&_table]:!border-[color:var(--border)] [&_td]:!border-[color:var(--border)] [&_th]:!border-[color:var(--border)]",
)}
>
<h1 className="font-medium text-2xl">
<div className="prose">
<h1 className="text-2xl font-medium">
Hi there! I&rsquo;m Jake.{" "}
<span className="ml-0.5 inline-block origin-[65%_80%] text-2xl motion-safe:animate-wave">
<span className="motion-safe:animate-wave ml-0.5 inline-block origin-[65%_80%] text-2xl">
👋
</span>
</h1>
@@ -31,125 +21,26 @@ const Page = () => (
area.
</h2>
<p>
I specialize in using TypeScript, React, and Next.js to make lightweight
frontends with dynamic and powerful backends.
</p>
<p>
Whenever possible, I also apply my experience in{" "}
<a
href="https://bugcrowd.com/jakejarvis"
title="Jake Jarvis on Bugcrowd"
target="_blank"
rel="noopener noreferrer"
>
information security
</a>{" "}
and{" "}
<a
href="https://github.com/jakejarvis?tab=repositories&q=github-actions&type=&language=&sort=stargazers"
title='My repositories tagged with "github-actions" on GitHub'
target="_blank"
rel="noopener noreferrer"
>
devops
</a>
.
</p>
<p>
I fell in love with{" "}
<Link
href="/previously"
title="My Terrible, Horrible, No Good, Very Bad First Websites"
>
frontend web design
</Link>{" "}
and{" "}
<Link
href="/notes/my-first-code"
title="Jake's Bulletin Board, circa 2003"
>
backend coding
</Link>{" "}
when my only source of income was{" "}
<Link
href="/birthday"
title="🎉 Cranky Birthday Boy on VHS Tape 📼"
style={{
cursor: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='30' style='font-size:24px'><text y='50%' transform='rotate(-70 0 0) translate(-20, 6)'>🪄</text></svg>") 5 5, auto`,
}}
className="font-normal no-underline"
>
the Tooth Fairy
</Link>
.{" "}
<span className="text-muted-foreground">
(I&rsquo;ve improved a bit since then, I think?)
</span>
</p>
<p>
I&rsquo;m currently building{" "}
<a
href="https://domainstack.io"
title="Domainstack: Domain intelligence made easy"
className="font-medium"
target="_blank"
rel="noopener noreferrer"
>
Domainstack
</a>
, a beautiful all-in-one domain name intelligence tool, and{" "}
<a
href="https://snoozle.ai"
title="Snoozle: AI-powered bedtime stories for children"
className="font-medium"
target="_blank"
rel="noopener noreferrer"
>
Snoozle
</a>
, an AI-powered bedtime story generator.
</p>
<p className="mt-2 mb-0 text-sm leading-normal">
You can find my work on{" "}
<a
href="https://github.com/jakejarvis"
target="_blank"
rel="noopener noreferrer me"
>
GitHub
</a>{" "}
and{" "}
<a
href="https://www.linkedin.com/in/jakejarvis/"
target="_blank"
rel="noopener noreferrer me"
>
LinkedIn
</a>
. I&rsquo;m always available to connect over{" "}
<Link href="/contact" title="Send an email">
I&rsquo;m always available to connect over{" "}
<a href="mailto:jake@jarv.is" title="Send an email">
email
</Link>{" "}
</a>{" "}
<sup className="">
<a
href="https://keyoxide.org/hkp/3bc6e5776bf379d36f6714802b0c9cf251e69a39"
target="_blank"
rel="noopener pgpkey"
title="Download my PGP key"
className="not-prose space-x-1 text-nowrap px-0.5 text-muted-foreground no-underline hover:text-primary hover:no-underline"
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-wrap text-[9px] leading-none tracking-wider [word-spacing:-3px]">
<code className="text-[9px] leading-none tracking-wider text-wrap [word-spacing:-3px]">
2B0C 9CF2 51E6 9A39
</code>
</a>
</sup>{" "}
as well.
</sup>
.
</p>
</div>
);
+1
View File
@@ -1,6 +1,7 @@
"use client";
import { useEffect } from "react";
import { ComicNeue } from "@/lib/fonts";
export const PageStyles = () => {
+2 -1
View File
@@ -6,7 +6,8 @@ import { PageStyles } from "./page-styles";
export const metadata = createMetadata({
title: "Previously on...",
description: "An incredibly embarrassing and somewhat painful trip down this site's memory lane...",
description:
"An incredibly embarrassing and somewhat painful trip down this site's memory lane...",
canonical: "/previously",
});
-2
View File
@@ -1,5 +1,4 @@
import "server-only";
import { graphql } from "@octokit/graphql";
import type { Repository, User } from "@octokit/graphql-schema";
import * as cheerio from "cheerio";
@@ -40,7 +39,6 @@ export const getContributions = async (): Promise<
const dayTooltips = $(".js-calendar-graph tool-tip")
.toArray()
// biome-ignore lint/suspicious/noExplicitAny: cheerio DOM element map
.reduce<Record<string, any>>((map, elem) => {
map[elem.attribs.for] = elem;
return map;
+38 -48
View File
@@ -1,14 +1,15 @@
import { ExternalLinkIcon, GitForkIcon, StarIcon } from "lucide-react";
import { notFound } from "next/navigation";
import { Suspense } from "react";
import { ActivityCalendar } from "@/components/activity-calendar";
import { PageTitle } from "@/components/layout/page-title";
import { RelativeTime } from "@/components/relative-time";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { createMetadata } from "@/lib/metadata";
import { cn } from "@/lib/utils";
import { getContributions, getRepos } from "./github";
export const metadata = createMetadata({
@@ -21,23 +22,18 @@ const Page = async () => {
// don't fail the entire site build if the required config for this page is missing, just return a 404 since this page
// would be mostly blank anyways.
if (!process.env.GITHUB_TOKEN) {
console.error(
"[/projects] I can't fetch anything from GitHub without 'GITHUB_TOKEN' set!",
);
console.error("[/projects] I can't fetch anything from GitHub without 'GITHUB_TOKEN' set!");
notFound();
}
// fetch the repos and contributions in parallel
const [contributions, repos] = await Promise.all([
getContributions(),
getRepos(),
]);
const [contributions, repos] = await Promise.all([getContributions(), getRepos()]);
return (
<>
<PageTitle canonical="/projects">Projects</PageTitle>
<h2 className="my-3.5 font-medium text-xl">
<h2 className="my-3.5 text-xl font-medium">
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}`}
target="_blank"
@@ -54,13 +50,13 @@ const Page = async () => {
<ActivityCalendar data={contributions} noun="contribution" />
</div>
) : (
<p className="my-4 text-center text-muted-foreground">
<p className="text-muted-foreground my-4 text-center">
Unable to load contribution data at this time.
</p>
)}
</Suspense>
<h2 className="my-3.5 font-medium text-xl">
<h2 className="my-3.5 text-xl font-medium">
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&sort=stargazers`}
target="_blank"
@@ -76,32 +72,31 @@ const Page = async () => {
{repos.map((repo) => (
<div
key={repo?.name}
className="h-fit space-y-1.5 rounded-2xl border-1 border-ring/30 px-4 py-3 shadow-xs"
className="border-ring/30 h-fit space-y-1.5 rounded-2xl border-1 px-4 py-3 shadow-xs"
>
<a
href={repo?.url}
target="_blank"
rel="noopener noreferrer"
className="inline-block font-semibold text-[#0969da] text-base leading-relaxed hover:underline dark:text-[#76affa]"
className="inline-block text-base leading-relaxed font-semibold text-[#0969da] hover:underline dark:text-[#76affa]"
>
{repo?.name}
</a>
{repo?.description && (
<p className="text-[13px] text-foreground/85 leading-relaxed">
<p className="text-foreground/85 text-[13px] leading-relaxed">
{repo?.description}
</p>
)}
<div className="flex flex-wrap gap-x-4 whitespace-nowrap text-xs leading-loose">
<div className="flex flex-wrap gap-x-4 text-xs leading-loose whitespace-nowrap">
{repo?.primaryLanguage && (
<div className="inline-flex flex-nowrap items-center gap-1.5 text-muted-foreground">
<div className="text-muted-foreground inline-flex flex-nowrap items-center gap-1.5">
{repo?.primaryLanguage.color && (
<span
className="inline-block size-3 rounded-full bg-[var(--language-color)]"
style={{
["--language-color" as string]:
repo?.primaryLanguage.color,
["--language-color" as string]: repo?.primaryLanguage.color,
}}
/>
)}
@@ -115,16 +110,13 @@ const Page = async () => {
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.stargazerCount)} ${repo?.stargazerCount === 1 ? "star" : "stars"}`}
target="_blank"
rel="noopener noreferrer"
className="inline-flex flex-nowrap items-center gap-1.5 text-muted-foreground hover:text-primary hover:no-underline"
className="text-muted-foreground hover:text-primary inline-flex flex-nowrap items-center gap-1.5 hover:no-underline"
>
<StarIcon
className="inline-block size-3.5 shrink-0"
aria-hidden="true"
/>
<StarIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
<span>
{Intl.NumberFormat(
process.env.NEXT_PUBLIC_SITE_LOCALE,
).format(repo?.stargazerCount)}
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(
repo?.stargazerCount,
)}
</span>
</a>
)}
@@ -135,21 +127,18 @@ const Page = async () => {
target="_blank"
rel="noopener noreferrer"
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.forkCount)} ${repo?.forkCount === 1 ? "fork" : "forks"}`}
className="inline-flex flex-nowrap items-center gap-1.5 text-muted-foreground hover:text-primary hover:no-underline"
className="text-muted-foreground hover:text-primary inline-flex flex-nowrap items-center gap-1.5 hover:no-underline"
>
<GitForkIcon
className="inline-block size-3.5 shrink-0"
aria-hidden="true"
/>
<GitForkIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
<span>
{Intl.NumberFormat(
process.env.NEXT_PUBLIC_SITE_LOCALE,
).format(repo?.forkCount)}
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(
repo?.forkCount,
)}
</span>
</a>
)}
<div className="whitespace-nowrap text-muted-foreground">
<div className="text-muted-foreground whitespace-nowrap">
<Suspense fallback={null}>
<span>
Updated <RelativeTime date={repo?.pushedAt} />
@@ -161,24 +150,25 @@ const Page = async () => {
))}
</div>
) : (
<p className="my-4 text-center text-muted-foreground">
<p className="text-muted-foreground my-4 text-center">
Unable to load repository data at this time.
</p>
)}
<p className="mt-6 mb-0 text-center font-medium text-base">
<Button variant="link" asChild>
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&type=source&sort=stargazers`}
target="_blank"
rel="noopener noreferrer"
>
View all
<ExternalLinkIcon
className="inline-block size-3.5 shrink-0"
aria-hidden="true"
<p className="mt-6 mb-0 text-center text-base font-medium">
<Button
variant="link"
nativeButton={false}
render={
<a
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_USERNAME}?tab=repositories&type=source&sort=stargazers`}
target="_blank"
rel="noopener noreferrer"
/>
</a>
}
>
View all
<ExternalLinkIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
</Button>
</p>
</>
+1 -1
View File
@@ -1,4 +1,5 @@
import path from "node:path";
import glob from "fast-glob";
import type { MetadataRoute } from "next";
@@ -19,7 +20,6 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
const routes: MetadataRoute.Sitemap = [
{
// homepage
// biome-ignore lint/style/noNonNullAssertion: expected to be set in env
url: process.env.NEXT_PUBLIC_BASE_URL!,
priority: 1.0,
lastModified: new Date(),
+26 -14
View File
@@ -17,34 +17,46 @@ export const Terminal = () => (
}}
>
<code className="border-ring block rounded-lg border border-solid bg-black/60 p-4 text-sm break-all text-white/90 backdrop-blur-sm backdrop-saturate-150">
<span style={{ color: "#f95757" }}>sundar</span>@<span style={{ color: "#3b9dd2" }}>google</span>:
<span style={{ color: "#78df55" }}>~</span>$ <span style={{ color: "#d588fb" }}>mv</span> /root
<a href="https://killedbygoogle.com/" style={{ color: "inherit" }} className="hover:no-underline">
<span style={{ color: "#f95757" }}>sundar</span>@
<span style={{ color: "#3b9dd2" }}>google</span>:<span style={{ color: "#78df55" }}>~</span>${" "}
<span style={{ color: "#d588fb" }}>mv</span> /root
<a
href="https://killedbygoogle.com/"
style={{ color: "inherit" }}
className="hover:no-underline"
>
/stable_products_that_people_rely_on/
</a>
googledomains.zip /tmp/
<br />
<span style={{ color: "#f95757" }}>sundar</span>@<span style={{ color: "#3b9dd2" }}>google</span>:
<span style={{ color: "#78df55" }}>~</span>$ <span style={{ color: "#d588fb" }}>crontab</span>{" "}
<span style={{ color: "#fd992a" }}>-l</span>
<span style={{ color: "#f95757" }}>sundar</span>@
<span style={{ color: "#3b9dd2" }}>google</span>:<span style={{ color: "#78df55" }}>~</span>${" "}
<span style={{ color: "#d588fb" }}>crontab</span> <span style={{ color: "#fd992a" }}>-l</span>
<br />
<br />
<span style={{ color: "#929292" }}>
# TODO(someone else): make super duper sure this only deletes actual zip files and *NOT* the sketchy domains
ending with file extensions released by us & purchased on our registrar (which i just yeeted btw cuz i&apos;m &
also my evil superpowers are fueled by my reckless disregard for the greater good of the internet). - xoxo
sundar <span style={{ color: "#f95757" }}>&lt;3</span>
# TODO(someone else): make super duper sure this only deletes actual zip files and *NOT* the
sketchy domains ending with file extensions released by us & purchased on our registrar
(which i just yeeted btw cuz i&apos;m & also my evil superpowers are fueled by my reckless
disregard for the greater good of the internet). - xoxo sundar{" "}
<span style={{ color: "#f95757" }}>&lt;3</span>
</span>
<br />
<span style={{ color: "#78df55" }}>@monthly</span>&nbsp;&nbsp;&nbsp;&nbsp;
<span style={{ color: "#d588fb" }}>rm</span> <span style={{ color: "#fd992a" }}>-f</span> /tmp/
<a href="https://fuckyougoogle.zip/" style={{ color: "inherit" }} className="hover:no-underline">
<span style={{ color: "#d588fb" }}>rm</span> <span style={{ color: "#fd992a" }}>-f</span>{" "}
/tmp/
<a
href="https://fuckyougoogle.zip/"
style={{ color: "inherit" }}
className="hover:no-underline"
>
*.zip
</a>
<br />
<br />
<span style={{ color: "#f95757" }}>sundar</span>@<span style={{ color: "#3b9dd2" }}>google</span>:
<span style={{ color: "#78df55" }}>~</span>$ <span style={{ color: "#d588fb" }}>reboot</span> 0
<span style={{ color: "#f95757" }}>sundar</span>@
<span style={{ color: "#3b9dd2" }}>google</span>:<span style={{ color: "#78df55" }}>~</span>${" "}
<span style={{ color: "#d588fb" }}>reboot</span> 0
</code>
</div>
);