1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-06-27 17:05:42 -04:00

some more arguably unnecessary refactoring

This commit is contained in:
2025-05-05 22:24:25 -04:00
parent 27e6ca2a4b
commit 62e95e3cfe
50 changed files with 669 additions and 604 deletions

View File

@ -1,6 +1,6 @@
import { env } from "@/lib/env";
import { JsonLd } from "react-schemaorg";
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Video from "@/components/video";
import { createMetadata } from "@/lib/metadata";
import type { VideoObject } from "schema-dts";

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Comments from "@/components/comments";
import { createMetadata } from "@/lib/metadata";

View File

@ -72,20 +72,20 @@ const ContactForm = () => {
}}
disabled={pending || formState.success}
aria-invalid={!pending && formState.errors?.message ? "true" : undefined}
className="min-h-24"
className="min-h-[6lh] resize-y"
/>
{!pending && formState.errors?.message && (
<span className="text-destructive text-[0.8rem] font-semibold">{formState.errors.message[0]}</span>
)}
<div className="text-foreground/85 mt-2 text-[0.8rem] leading-relaxed">
<div className="text-foreground/85 my-2 text-[0.8rem] leading-relaxed">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
stroke="currentColor"
strokeWidth="0"
viewBox="0 0 24 24"
className="mr-1 inline-block size-[16px] align-text-top"
className="mr-1 inline-block size-4 align-text-top"
>
<path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z" />
</svg>{" "}
@ -102,9 +102,7 @@ const ContactForm = () => {
</div>
<div>
<div className="my-4">
<Turnstile sitekey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} fixedSize />
</div>
<Turnstile sitekey={env.NEXT_PUBLIC_TURNSTILE_SITE_KEY} fixedSize />
{!pending && formState.errors?.["cf-turnstile-response"] && (
<span className="text-destructive text-[0.8rem] font-semibold">
{formState.errors["cf-turnstile-response"][0]}
@ -112,7 +110,7 @@ const ContactForm = () => {
)}
</div>
<div className="mt-[0.6em] flex min-h-[3.75em] items-center">
<div className="flex min-h-16 items-center">
{!formState.success && (
<Button type="submit" size="lg" disabled={pending}>
{pending ? (
@ -126,16 +124,16 @@ const ContactForm = () => {
)}
</Button>
)}
{!pending && formState.message && (
<div
className={cn("ml-4 text-[0.9rem] font-semibold", formState.success ? "text-success" : "text-destructive")}
className={cn(
"ml-4 space-x-[2px] text-[0.9rem] font-semibold",
formState.success ? "text-success" : "text-destructive"
)}
>
{formState.success ? (
<CheckIcon className="inline size-[16px]" />
) : (
<XIcon className="inline size-[16px]" />
)}{" "}
<span className="ml-[2px]">{formState.message}</span>
{formState.success ? <CheckIcon className="inline size-4" /> : <XIcon className="inline size-4" />}{" "}
<span>{formState.message}</span>
</div>
)}
</div>

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Link from "@/components/link";
import { createMetadata } from "@/lib/metadata";

View File

@ -2,51 +2,53 @@
@custom-variant dark (&:where([data-theme=dark] *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.5 0.134 242.749);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--warning: oklch(0.67 0.179 58.318);
--success: oklch(0.63 0.194 149.214);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
}
@layer base {
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.5 0.134 242.749);
--primary-foreground: oklch(0.97 0.014 254.604);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--warning: oklch(0.67 0.179 58.318);
--success: oklch(0.63 0.194 149.214);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
}
[data-theme="dark"] {
--background: oklch(0.205 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.81 0.105 251.813);
--primary-foreground: oklch(0.18 0.0374 265.522);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--warning: oklch(0.8 0.184 86.047);
--success: oklch(0.79 0.209 151.711);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
[data-theme="dark"] {
--background: oklch(0.205 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.81 0.105 251.813);
--primary-foreground: oklch(0.18 0.0374 265.522);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--warning: oklch(0.8 0.184 86.047);
--success: oklch(0.79 0.209 151.711);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
}
}
@theme inline {
@ -58,8 +60,6 @@
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--container-default: var(--container-4xl);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@ -85,10 +85,13 @@
@theme {
--animate-wave: wave 5s ease 1s infinite;
--animate-heartbeat: heartbeat 10s ease 7.5s infinite;
--animate-loading: animation: loading 1.5s infinite ease-in-out both;
--animate-loading: loading 1.5s infinite ease-in-out both;
--animate-marquee: marquee 30s infinite linear;
@keyframes wave {
0% {
0%,
30%,
100% {
transform: rotate(0deg);
}
5% {
@ -106,33 +109,21 @@
25% {
transform: rotate(10deg);
}
30% {
transform: rotate(0deg);
}
100% {
transform: rotate(0deg);
}
}
@keyframes heartbeat {
0% {
0%,
4%,
8%,
100% {
transform: scale(1);
}
2% {
transform: scale(1.25);
}
4% {
transform: scale(1);
}
6% {
transform: scale(1.2);
}
8% {
transform: scale(1);
}
100% {
transform: scale(1);
}
}
@keyframes loading {
@ -145,4 +136,47 @@
transform: scale(0.6);
}
}
@keyframes marquee {
from {
transform: translateX(0);
}
to {
transform: translateX(calc(-100% - var(--gap)));
}
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
font-synthesis-weight: none;
font-variant-ligatures: none;
text-rendering: optimizeLegibility;
}
::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 5px;
}
* {
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
}
@layer components {
.youtube-embed {
@import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
}
}

View File

@ -1,6 +1,6 @@
import { env } from "@/lib/env";
import { JsonLd } from "react-schemaorg";
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Link from "@/components/link";
import Video from "@/components/video";
import { createMetadata } from "@/lib/metadata";

View File

@ -5,27 +5,24 @@ import { ThemeProvider, ThemeScript } from "@/components/layout/theme-context";
import Header from "@/components/layout/header";
import Footer from "@/components/layout/footer";
import { SkipNavLink, SkipNavTarget } from "@/components/layout/skip-nav";
import { cn } from "@/lib/utils";
import { defaultMetadata } from "@/lib/metadata";
import { GeistMono, GeistSans } from "@/lib/fonts";
import siteConfig from "@/lib/config/site";
import authorConfig from "@/lib/config/author";
import type { Person, WebSite } from "schema-dts";
import { GeistMono, GeistSans } from "./fonts";
import "./globals.css";
export const metadata = defaultMetadata;
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
<html
lang={env.NEXT_PUBLIC_SITE_LOCALE}
className={cn(GeistSans.variable, GeistMono.variable)}
suppressHydrationWarning
>
<html lang={env.NEXT_PUBLIC_SITE_LOCALE} suppressHydrationWarning>
<head>
<ThemeScript />
<style id="geist-font">{`:root{--font-geist-sans:${GeistSans.style.fontFamily};--font-geist-mono:${GeistMono.style.fontFamily};}`}</style>
<JsonLd<Person>
item={{
"@context": "https://schema.org",
@ -67,7 +64,7 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
<ThemeProvider>
<SkipNavLink />
<div className="max-w-default mx-auto w-full px-5">
<div className="mx-auto w-full max-w-4xl px-5">
<Header className="mt-4 mb-6 w-full" />
<main>

View File

@ -1,6 +1,6 @@
import { env } from "@/lib/env";
import { JsonLd } from "react-schemaorg";
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Link from "@/components/link";
import Video from "@/components/video";
import { createMetadata } from "@/lib/metadata";

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import { createMetadata } from "@/lib/metadata";
export const metadata = createMetadata({

View File

@ -85,20 +85,23 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
}}
/>
<div className="text-foreground/70 -mt-1 flex flex-wrap justify-items-start text-[0.8rem] leading-9 tracking-wide md:text-[0.85rem]">
<Link href={`/${POSTS_DIR}/${frontmatter!.slug}`} className={"text-foreground/70 mr-4 whitespace-nowrap"}>
<CalendarDaysIcon className="mr-2 inline size-[16px] shrink-0 align-text-bottom" />
<div className="text-foreground/70 -mt-1 flex flex-wrap justify-items-start space-x-4 text-[0.8rem] leading-9 tracking-wide md:text-[0.85rem]">
<Link
href={`/${POSTS_DIR}/${frontmatter!.slug}`}
className={"text-foreground/70 space-x-2 whitespace-nowrap hover:no-underline"}
>
<CalendarDaysIcon className="inline size-4 shrink-0 align-text-bottom" />
<Time date={frontmatter!.date} format="MMMM d, y" />
</Link>
{frontmatter!.tags && (
<div className="mr-4">
<TagIcon className="mr-2 inline size-[16px] shrink-0 align-text-bottom" />
<div className="space-x-2">
<TagIcon className="inline size-4 shrink-0 align-text-bottom" />
{frontmatter!.tags.map((tag) => (
<span
key={tag}
title={tag}
className="before:text-foreground/40 mr-2 lowercase before:pr-0.5 before:content-['\0023'] last-of-type:mr-0"
className="before:text-foreground/40 lowercase before:pr-0.5 before:content-['\0023'] last-of-type:mr-0"
aria-label={`Tagged with ${tag}`}
>
{tag}
@ -110,14 +113,14 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
<Link
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter!.slug}/index.mdx`}
title={`Edit "${frontmatter!.title}" on GitHub`}
className={"text-foreground/70 mr-4 whitespace-nowrap"}
className={"text-foreground/70 space-x-2 whitespace-nowrap hover:no-underline"}
>
<SquarePenIcon className="mr-2 inline size-[16px] shrink-0 align-text-bottom" />
<SquarePenIcon className="inline size-4 shrink-0 align-text-bottom" />
<span>Improve This Post</span>
</Link>
<div className="mr-0 min-w-10 whitespace-nowrap">
<EyeIcon className="mr-2 inline size-[16px] shrink-0 align-text-bottom" />
<div className="min-w-14 space-x-2 whitespace-nowrap">
<EyeIcon className="inline size-4 shrink-0 align-text-bottom" />
<Suspense
// when this loads, the component will count up from zero to the actual number of hits, so we can simply
// show a zero here as a "loading indicator"

View File

@ -196,9 +196,9 @@ const Page = () => {
href="https://jrvs.io/pgp"
rel="pgpkey"
title="My Public Key"
className="text-muted-foreground hover:decoration-muted-foreground/40 text-nowrap hover:decoration-1"
className="text-muted-foreground hover:decoration-muted-foreground/40 text-nowrap hover:decoration-1 hover:underline-offset-4"
>
<LockIcon className="mr-[1px] inline size-3 align-text-top" />
<LockIcon className="mr-0.5 inline size-3 align-text-top" />
<code className="mx-0.5 tracking-wider text-wrap [word-spacing:-4px]">2B0C 9CF2 51E6 9A39</code>
</Link>
</sup>

View File

@ -1,6 +1,7 @@
import { Comic_Neue as ComicNeueLoader } from "next/font/google";
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Marquee from "@/components/marquee";
import { createMetadata } from "@/lib/metadata";
import { ComicNeue } from "@/lib/fonts";
export const metadata = createMetadata({
title: "Previously on...",
@ -8,45 +9,42 @@ export const metadata = createMetadata({
canonical: "/previously",
});
export const ComicNeue = ComicNeueLoader({
weight: ["400", "700"],
style: ["normal", "italic"],
subsets: ["latin"],
display: "swap",
fallback: ["'Comic Sans MS'", "'Comic Sans'"],
adjustFontFallback: false,
variable: "--font-comic-neue",
preload: false,
});
export const WarningMarquee = () => (
<Marquee>
<span>
🚨 Trigger warning: excessive marquees, animated GIFs, Comic Sans, popups,{" "}
<code style={{ fontWeight: "normal", fontSize: "0.9em" }}>
color: <span style={{ color: "#32cd32" }}>limegreen</span>
</code>{" "}
ahead...
</span>
</Marquee>
);
export const PageStyles = () => (
<style
// this is really, really dumb idea but f*ck it we'll do it live
dangerouslySetInnerHTML={{
__html: `
body {
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAZklEQVR4AWIAgn/uBT6A9uoAAwAQiIJo97/0Rgy0ANoJH8MPeEgtqwPQEACqCoQHAKECQKgAECoAhAoAoQJAqAAQxh1oPQfcW3kJpxHtL1AAHAwEwwdYiH8BIEgBTBRAAAEEEEAAG7mRt30hEhoLAAAAAElFTkSuQmCC") 2 1, auto;
}
a, button {
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAACVBMVEVHcEwAAAD///8W1S+BAAAAAXRSTlMAQObYZgAAAEdJREFUeAFjoAVghTGkHIhghMAYmQEwxlIYYxlYlSiQMQEsELUKyli1ahWYwQZjMGIwGLKQGA4QA1EYEP0rGVAZrKGhSF4BAHw/HsVwshytAAAAAElFTkSuQmCC") 16 12, auto;
}
main {
font-family: ${ComicNeue.style.fontFamily}, var(--default-font-family) !important;
font-weight: 700 !important;
font-size: 1em !important;
text-align: center;
}
main iframe + p em,
main img + em {
display: block;
text-align: center;
font-style: normal;
font-size: 0.95em;
font-weight: 700;
}
`.trim(),
}}
/>
<style>
{`body {
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAZklEQVR4AWIAgn/uBT6A9uoAAwAQiIJo97/0Rgy0ANoJH8MPeEgtqwPQEACqCoQHAKECQKgAECoAhAoAoQJAqAAQxh1oPQfcW3kJpxHtL1AAHAwEwwdYiH8BIEgBTBRAAAEEEEAAG7mRt30hEhoLAAAAAElFTkSuQmCC") 2 1, auto;
}
a, button {
cursor: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAACVBMVEVHcEwAAAD///8W1S+BAAAAAXRSTlMAQObYZgAAAEdJREFUeAFjoAVghTGkHIhghMAYmQEwxlIYYxlYlSiQMQEsELUKyli1ahWYwQZjMGIwGLKQGA4QA1EYEP0rGVAZrKGhSF4BAHw/HsVwshytAAAAAElFTkSuQmCC") 16 12, auto;
}
main {
font-family: ${ComicNeue.style.fontFamily}, var(--default-font-family) !important;
font-weight: 700 !important;
font-size: 1em !important;
text-align: center;
}
main iframe + p em,
main img + em {
display: block;
text-align: center;
font-style: normal;
font-size: 0.95em;
font-weight: 700;
}
`}
</style>
);
export const WindowsLogo = () => (
@ -56,7 +54,7 @@ export const WindowsLogo = () => (
stroke="currentColor"
strokeWidth="0"
viewBox="0 0 24 24"
className="inline size-[16px] align-text-top"
className="inline size-4 align-text-top"
>
<path d="M5.712 1.596l-.756.068-.238.55.734-.017zm1.39.927l-.978.137-.326.807.96-.12.345-.824zM4.89 3.535l-.72.05-.24.567.721-.017zm3.724.309l-1.287.068-.394.96 1.27-.052zm1.87.566l-1.579.069-.566 1.357 1.596-.088.548-1.338zm-4.188.037l-.977.153-.343.806.976-.12zm6.144.668l-1.87.135-.637 1.527 1.87-.154zm2.925.219c-.11 0-.222 0-.334.002l-.767 1.85c1.394-.03 2.52.089 3.373.38l-1.748 4.201c-.955-.304-2.082-.444-3.36-.394l-.54 1.305a8.762 8.762 0 0 1 3.365.396l-1.663 4.014c-1.257-.27-2.382-.395-3.387-.344l-.782 1.887c3.363-.446 6.348.822 9.009 3.773L24 9.23c-2.325-2.575-5.2-3.88-8.637-3.896zm-.644.002l-2.024.12-.687 1.68 2.025-.19zm-10.603.05l-.719.036-.224.566h.703l.24-.601zm3.69.397l-1.287.069-.395.959 1.27-.05zM5.54 6.3l-.994.154-.344.807.98-.121zm4.137.066l-1.58.069L7.53 7.77l1.596-.085.55-1.32zm1.955.688l-1.87.135-.636 1.527 1.887-.154zm2.282.19l-2.01.136-.7 1.682 2.04-.19.67-1.63zm-10.57.066l-.739.035-.238.564h.72l.257-.6zm3.705.293l-1.303.085-.394.96 1.287-.034zm11.839.255a6.718 6.718 0 0 1 2.777 1.717l-1.75 4.237c-.617-.584-1.15-.961-1.611-1.149l-1.201-.498zM4.733 8.22l-.976.154-.344.807.961-.12.36-.841zm4.186 0l-1.594.052-.549 1.354L8.37 9.54zm1.957.668L8.99 9.04l-.619 1.508 1.87-.135.636-1.527zm2.247.275l-2.007.12-.703 1.665 2.042-.156zM2.52 9.267l-.718.033-.24.549.718-.016zm3.725.273l-1.289.07-.41.96 1.287-.03.412-1zm1.87.6l-1.596.05-.55 1.356 1.598-.084.547-1.322zm-4.186.037l-.979.136-.324.805.96-.119zm6.14.633l-1.87.154-.653 1.527 1.906-.154zm2.267.275l-2.026.12-.686 1.663 2.025-.172zm-10.569.031l-.739.037-.238.565.72-.016zm3.673.362l-1.289.068-.41.978 1.305-.05zm-2.285.533l-.976.154-.326.805.96-.12.342-.84zm4.153.07l-1.596.066-.565 1.356 1.612-.084zm1.957.666l-1.889.154-.617 1.526 1.886-.15zm2.28.223l-2.025.12-.685 1.665 2.041-.172.67-1.613zm-10.584.05l-.738.053L0 13.64l.72-.02.24-.6zm3.705.31l-1.285.07-.395.976 1.287-.05.393-.997zm11.923.07c1.08.29 2.024.821 2.814 1.613l-1.715 4.183c-.892-.754-1.82-1.32-2.814-1.664l1.715-4.133zm-10.036.515L4.956 14l-.549 1.32 1.578-.066.567-1.338zm-4.184.014l-.996.156-.309.79.961-.106zm6.14.67l-1.904.154-.617 1.527 1.89-.154.632-1.527zm2.231.324l-2.025.123-.686 1.682 2.026-.174zm-6.863.328l-1.3.068-.397.98 1.285-.054zm1.871.584l-1.578.068-.566 1.334 1.595-.064zm1.953.701l-1.867.137-.635 1.51 1.87-.137zm2.23.31l-2.005.122-.703 1.68 2.04-.19.67-1.61z" />
</svg>
@ -73,9 +71,9 @@ _Previously on the [Cringey Chronicles&trade;](https://web.archive.org/web/20010
---
🚨 Trigger warning: excessive marquees, animated GIFs, Comic Sans, popups, <code style={{ fontWeight: "normal", fontSize: "0.9em" }}>color: <span style={{ color: "#32cd32" }}>limegreen</span></code> ahead...
<WarningMarquee />
[<WindowsLogo /> Click here for the _full_ experience anyway.](https://y2k.pages.dev)
[<WindowsLogo /> Click here for the _full_ experience (at your own risk).](https://y2k.pages.dev)
<iframe
src="https://jakejarvis.github.io/my-first-website/"

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import { createMetadata } from "@/lib/metadata";
export const metadata = createMetadata({

View File

@ -2,13 +2,13 @@ import { env } from "@/lib/env";
import { Suspense } from "react";
import { notFound } from "next/navigation";
import { GitForkIcon, StarIcon } from "lucide-react";
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Link from "@/components/link";
import RelativeTime from "@/components/relative-time";
import ActivityCalendar from "./activity-calendar";
import ActivityCalendar from "@/components/activity-calendar";
import { cn } from "@/lib/utils";
import { createMetadata } from "@/lib/metadata";
import { getContributions, getRepos } from "./github";
import { getContributions, getRepos } from "./api";
export const metadata = createMetadata({
title: "Projects",
@ -41,15 +41,8 @@ const Page = async () => {
</h2>
<Suspense fallback={<p>Failed to generate activity calendar.</p>}>
<div
className={cn(
"mx-auto mt-4 mb-8",
String.raw`[&_:where(.react-activity-calendar\_\_count,.react-activity-calendar\_\_legend-month,.react-activity-calendar\_\_legend-colors)]:text-muted-foreground`,
"[--activity-0:#ebedf0] [--activity-1:#9be9a8] [--activity-2:#40c463] [--activity-3:#30a14e] [--activity-4:#216e39]",
"dark:[--activity-0:#252525] dark:[--activity-1:#033a16] dark:[--activity-2:#196c2e] dark:[--activity-3:#2ea043] dark:[--activity-4:#56d364]"
)}
>
<ActivityCalendar data={contributions} />
<div className={cn("mx-auto mt-4 mb-8")}>
<ActivityCalendar data={contributions} noun="contribution" />
</div>
</Suspense>
@ -76,7 +69,7 @@ const Page = async () => {
<div className="mt-1 whitespace-nowrap">
{repo!.primaryLanguage.color && (
<span
className="mr-2 inline-block size-[16px] align-text-top"
className="mr-2 inline-block size-4 align-text-top"
style={{ backgroundColor: repo!.primaryLanguage.color, borderRadius: "50%" }}
/>
)}
@ -91,7 +84,7 @@ const Page = async () => {
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.stargazerCount)} ${repo!.stargazerCount === 1 ? "star" : "stars"}`}
className="hover:text-primary text-muted-foreground hover:no-underline"
>
<StarIcon className="mr-2 inline-block size-[16px] align-text-top" />
<StarIcon className="mr-2 inline-block size-4 align-text-top" />
<span>{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.stargazerCount)}</span>
</Link>
</div>
@ -104,7 +97,7 @@ const Page = async () => {
title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.forkCount)} ${repo!.forkCount === 1 ? "fork" : "forks"}`}
className="hover:text-primary text-muted-foreground hover:no-underline"
>
<GitForkIcon className="mr-2 inline-block size-[16px] align-text-top" />
<GitForkIcon className="mr-2 inline-block size-4 align-text-top" />
<span>{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.forkCount)}</span>
</Link>
</div>
@ -113,7 +106,7 @@ const Page = async () => {
<div className="mt-1 whitespace-nowrap">
<span
// invisible icon hack to fix line height
className="mr-0 inline-block h-[16px] w-0 align-text-top"
className="mr-0 inline-block h-4 w-0 align-text-top"
/>
<span>
Updated <RelativeTime date={repo!.pushedAt} />
@ -130,7 +123,7 @@ const Page = async () => {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="fill-foreground/80 mx-0.5 inline size-[20px] align-text-top"
className="fill-foreground/80 mx-0.5 inline size-5 align-text-top"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>{" "}

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Comments from "@/components/comments";
import { createMetadata } from "@/lib/metadata";

View File

@ -1,4 +1,4 @@
import PageTitle from "@/components/page-title";
import PageTitle from "@/components/layout/page-title";
import Comments from "@/components/comments";
import { createMetadata } from "@/lib/metadata";

View File

@ -13,7 +13,7 @@
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},

View File

@ -6,18 +6,28 @@ import { Tooltip } from "react-tooltip";
import { format } from "date-fns";
import type { ComponentPropsWithoutRef } from "react";
import type { Activity } from "react-activity-calendar";
import "react-tooltip/dist/react-tooltip.css";
import { cn } from "@/lib/utils";
const Calendar = ({
data,
noun = "thing",
className,
...rest
}: ComponentPropsWithoutRef<"div"> & {
data: Activity[];
noun?: string;
}) => {
// heavily inspired by https://github.com/grubersjoe/react-github-calendar
return (
<div {...rest}>
<div
className={cn(
String.raw`**:[.react-activity-calendar\_\_count,.react-activity-calendar\_\_legend-month,.react-activity-calendar\_\_legend-colors]:text-muted-foreground`,
"[--activity-0:#ebedf0] [--activity-1:#9be9a8] [--activity-2:#40c463] [--activity-3:#30a14e] [--activity-4:#216e39]",
"dark:[--activity-0:#252525] dark:[--activity-1:#033a16] dark:[--activity-2:#196c2e] dark:[--activity-3:#2ea043] dark:[--activity-4:#56d364]",
className
)}
{...rest}
>
<ActivityCalendar
data={data}
colorScheme="dark"
@ -32,13 +42,13 @@ const Calendar = ({
],
}}
labels={{
totalCount: "{{count}} contributions in the last year",
totalCount: `{{count}} ${noun}s in the last year`,
}}
maxLevel={4}
renderBlock={(block, activity) =>
cloneElement(block, {
"data-tooltip-id": "activity-tooltip",
"data-tooltip-html": `${activity.count === 0 ? "No" : activity.count} contribution${activity.count === 1 ? "" : "s"} on ${format(activity.date, "MMMM do")}`,
"data-tooltip-html": `${activity.count === 0 ? "No" : activity.count} ${noun}${activity.count === 1 ? "" : "s"} on ${format(activity.date, "MMMM do")}`,
})
}
fontSize={13}

View File

@ -1,14 +1,15 @@
import { codeToHtml } from "shiki";
import reactToText from "react-to-text";
import CopyButton from "@/components/copy-button";
import { cn } from "@/lib/utils";
import type { JSX } from "react";
import type { ComponentProps, ComponentPropsWithoutRef } from "react";
const CodeBlock = async ({
lineNumbers,
lineNumbers = false,
className,
children,
...rest
}: JSX.IntrinsicElements["pre"] & {
}: ComponentPropsWithoutRef<"pre"> & {
lineNumbers?: boolean;
}) => {
// escape hatch if this code wasn't meant to be highlighted
@ -20,11 +21,13 @@ const CodeBlock = async ({
);
}
const codeProps = children.props as JSX.IntrinsicElements["code"];
const codeRaw = String(codeProps.children).trim();
const codeProps = children.props as ComponentProps<"code">;
const codeString = reactToText(codeProps.children).trim();
// the language set in the markdown is passed as a className
const lang = codeProps.className?.split("language-")[1] ?? "";
const codeHighlighted = await codeToHtml(codeRaw.trim(), {
const codeHighlighted = await codeToHtml(codeString, {
lang,
themes: {
light: "material-theme-lighter",
@ -33,24 +36,20 @@ const CodeBlock = async ({
});
return (
<div
className={cn(
"bg-muted/35 border-border relative my-4 w-full rounded-lg border-2 font-mono [font-variant-ligatures:none]",
className
)}
>
<div className={cn("bg-muted/35 relative rounded-lg border-2 font-mono", className)}>
<div
className={cn(
"grid w-full overflow-x-auto p-4 text-sm leading-none [counter-reset:line] dark:**:text-[var(--shiki-dark)]! [&_.line]:inline-block [&_.line]:min-w-full [&_.line]:py-1 [&_.line]:whitespace-pre [&_.line]:after:hidden [&>pre]:bg-transparent! [&>pre]:whitespace-normal",
"grid max-h-[500px] w-full overflow-x-auto p-4 **:bg-transparent! md:max-h-[650px] dark:**:text-[var(--shiki-dark)]! [&_pre]:whitespace-normal",
"[&_.line]:inline-block [&_.line]:min-w-full [&_.line]:py-1 [&_.line]:leading-none [&_.line]:whitespace-pre [&_.line]:after:hidden",
lineNumbers &&
"[&_.line]:before:text-muted-foreground [&_.line]:before:mr-4 [&_.line]:before:inline-block [&_.line]:before:w-5 [&_.line]:before:text-right [&_.line]:before:content-[counter(line)] [&_.line]:before:[counter-increment:line]"
"[&_.line]:before:text-muted-foreground [counter-reset:line] [&_.line]:before:mr-5 [&_.line]:before:inline-block [&_.line]:before:w-5 [&_.line]:before:text-right [&_.line]:before:content-[counter(line)] [&_.line]:before:[counter-increment:line]"
)}
data-language={lang}
dangerouslySetInnerHTML={{ __html: codeHighlighted }}
/>
<CopyButton
source={codeRaw}
className="border-border text-foreground/85 hover:text-primary bg-muted/10 absolute top-0 right-0 size-[40px] rounded-tr-lg rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs [&>svg]:my-auto [&>svg]:inline-block [&>svg]:size-[18px] [&>svg]:align-middle"
source={codeString}
className="text-foreground/85 hover:text-primary bg-muted/10 absolute top-0 right-0 size-10 rounded-tr-lg rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs *:[svg]:my-auto *:[svg]:inline-block *:[svg]:size-4.5 *:[svg]:align-text-bottom"
/>
</div>
);

View File

@ -0,0 +1,22 @@
import reactToText from "react-to-text";
import { LinkIcon } from "lucide-react";
import { cn } from "@/lib/utils";
const HeadingAnchor = ({ id, title, className }: { id: string; title: string; className?: string }) => {
return (
<a
href={`#${id}`}
className={cn(
"text-muted-foreground hover:text-primary ml-2 inline-block px-2 align-baseline hover:no-underline",
className
)}
aria-hidden="true"
tabIndex={-1}
>
<LinkIcon className="inline-block size-4 align-baseline" />
<span className="sr-only">Permalink to &ldquo;{reactToText(title)}&rdquo;</span>
</a>
);
};
export default HeadingAnchor;

View File

@ -1,25 +0,0 @@
import NextImage from "next/image";
import { MAX_WIDTH } from "@/lib/config/constants";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
import type { StaticImageData } from "next/image";
const Image = ({ src, height, width, placeholder, className, ...rest }: ComponentPropsWithoutRef<typeof NextImage>) => {
// ensure that the image width is not greater than the global maximum width
const constrainWidth = (width?: number | `${number}`) => {
return width ? Math.min(typeof width === "string" ? parseInt(width, 10) : width, MAX_WIDTH) : MAX_WIDTH;
};
return (
<NextImage
src={src}
height={height}
width={constrainWidth(width || (src as StaticImageData).width)}
placeholder={placeholder || (typeof src === "object" && "blurDataURL" in src ? "blur" : "empty")}
className={cn("mx-auto block h-auto max-w-full", className)}
{...rest}
/>
);
};
export default Image;

View File

@ -25,13 +25,13 @@ const Footer = ({ className, ...rest }: ComponentPropsWithoutRef<"footer">) => {
<div>
Made with{" "}
<HeartIcon className="animate-heartbeat stroke-destructive fill-destructive mx-[1px] inline size-[16px] align-text-top" />{" "}
<HeartIcon className="animate-heartbeat stroke-destructive fill-destructive mx-0.5 inline size-4 align-text-top" />{" "}
and{" "}
<Link
href="https://nextjs.org/"
title="Powered by Next.js"
aria-label="Next.js"
className="text-foreground/85 hover:text-muted-foreground/60 hover:no-underline"
className="text-foreground/85 hover:text-foreground/60 hover:no-underline"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -39,7 +39,7 @@ const Footer = ({ className, ...rest }: ComponentPropsWithoutRef<"footer">) => {
stroke="currentColor"
strokeWidth="0"
viewBox="0 0 24 24"
className="mx-[1px] inline size-[16px] align-text-top"
className="mx-0.5 inline size-4 align-text-top"
>
<path d="M18.665 21.978C16.758 23.255 14.465 24 12 24 5.377 24 0 18.623 0 12S5.377 0 12 0s12 5.377 12 12c0 3.583-1.574 6.801-4.067 9.001L9.219 7.2H7.2v9.596h1.615V9.251l9.85 12.727Zm-3.332-8.533 1.6 2.061V7.2h-1.6v6.245Z" />
</svg>
@ -48,7 +48,7 @@ const Footer = ({ className, ...rest }: ComponentPropsWithoutRef<"footer">) => {
<Link
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}`}
title="View Source on GitHub"
className="border-muted-foreground text-foreground/85 hover:border-muted-foreground/60 border-b-2 pb-0.5 hover:no-underline"
className="border-muted-foreground text-foreground/85 hover:border-muted-foreground/60 border-b-1 pb-0.5 hover:no-underline"
>
View source.
</Link>

View File

@ -1,7 +1,7 @@
import Link from "@/components/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
import type { LucideIcon } from "lucide-react";
import type { MenuItemConfig } from "@/lib/config/menu";
const MenuItem = ({
text,
@ -10,17 +10,15 @@ const MenuItem = ({
current,
className,
...rest
}: Omit<ComponentPropsWithoutRef<typeof Link>, "href"> & {
text?: string;
href?: string;
icon?: LucideIcon;
current?: boolean;
}) => {
}: Omit<ComponentPropsWithoutRef<typeof Link>, "href"> &
MenuItemConfig & {
current?: boolean;
}) => {
const Icon = icon;
const item = (
<>
{Icon && <Icon className="stroke-foreground/85 block size-[28px] md:size-[20px]" />}
{Icon && <Icon className="stroke-foreground/85 block h-7 w-7 md:h-5 md:w-5" />}
{text && <span className="ml-3 text-sm leading-none font-medium tracking-wide max-md:sr-only">{text}</span>}
</>
);

View File

@ -11,18 +11,21 @@ const Menu = ({ className, ...rest }: ComponentPropsWithoutRef<"ul">) => {
const segment = useSelectedLayoutSegment() || "";
return (
<ul className={cn("flex max-w-2/3 flex-row justify-between md:max-w-none md:justify-end", className)} {...rest}>
<ul
className={cn("flex max-w-2/3 flex-row justify-between md:max-w-none md:justify-end md:space-x-4", className)}
{...rest}
>
{menuItems.map((item) => {
const isCurrent = item.href === `/${segment}`;
const isCurrent = item.href?.split("/")[1] === segment;
return (
<li className="max-sm:first-of-type:hidden md:ml-4" key={item.href}>
<li className="inline-block max-sm:first-of-type:hidden" key={item.href}>
<MenuItem {...item} current={isCurrent} />
</li>
);
})}
<li className="-mr-2.5 md:ml-4">
<li className="-mr-2.5 inline-block">
<MenuItem
// @ts-ignore
icon={ThemeToggle}

View File

@ -13,10 +13,10 @@ const ThemeToggle = ({ ...rest }: ComponentPropsWithoutRef<LucideIcon>) => {
<button
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
aria-label="Toggle Theme"
className="hover:[&_svg]:stroke-warning block bg-transparent p-2.5 hover:cursor-pointer not-dark:[&_.lucide-moon]:hidden dark:[&_.lucide-sun]:hidden"
className="hover:*:stroke-warning block bg-transparent p-2.5 hover:cursor-pointer not-dark:[&_.lucide-moon]:hidden dark:[&_.lucide-sun]:hidden"
>
<SunIcon {...rest} />
<MoonIcon {...rest} />
<SunIcon aria-label="Light Mode" {...rest} />
<MoonIcon aria-label="Dark Mode" {...rest} />
</button>
);
};

View File

@ -19,27 +19,27 @@ const Link = ({
const isExternal = typeof href === "string" && !["/", "#"].includes(href[0]);
const linkProps = {
href,
target: target || (isExternal ? "_blank" : undefined),
rel: `${rel ? `${rel} ` : ""}${target === "_blank" || isExternal ? "noopener noreferrer" : ""}`.trim() || undefined,
className: cn(
"text-primary hover:decoration-primary/40 hover:underline hover:decoration-2 hover:underline-offset-4",
className
),
} satisfies ComponentPropsWithoutRef<"a">;
...rest,
} as ComponentPropsWithoutRef<"a">;
// don't waste time with next's component if it's just an external link
if (isExternal) {
return <a href={href} {...linkProps} {...rest} />;
return <a {...linkProps} />;
}
return (
<NextLink
href={href}
{...linkProps}
prefetch={dynamicOnHover ? null : prefetch}
// @ts-expect-error
unstable_dynamicOnHover={dynamicOnHover}
{...linkProps}
{...rest}
/>
);
};

View File

@ -2,14 +2,14 @@ import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Loading = ({
width,
width = 40,
boxes = 3,
timing = 0.1,
className,
style,
...rest
}: ComponentPropsWithoutRef<"div"> & {
width: number; // of entire container, in pixels
width?: number; // of entire container, in pixels
boxes?: number; // total number of boxes (default: 3)
timing?: number; // staggered timing between each box's pulse, in seconds (default: 0.1s)
}) => {

37
components/marquee.tsx Normal file
View File

@ -0,0 +1,37 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
// https://magicui.design/docs/components/marquee
const Marquee = ({
reverse = false,
pauseOnHover = false,
repeat = 3,
className,
children,
...rest
}: ComponentPropsWithoutRef<"div"> & {
reverse?: boolean;
pauseOnHover?: boolean;
repeat?: number;
}) => {
return (
<div className={cn("group flex flex-row [gap:var(--gap)] overflow-hidden [--gap:2rem]", className)} {...rest}>
{Array(repeat)
.fill(0)
.map((_, i) => (
<div
key={i}
className={cn(
"motion-safe:animate-marquee flex shrink-0 flex-row justify-around [gap:var(--gap)]",
pauseOnHover && "group-hover:[animation-play-state:paused]",
reverse && "[animation-direction:reverse]"
)}
>
{children}
</div>
))}
</div>
);
};
export default Marquee;

View File

@ -1,41 +0,0 @@
import { unstable_cache as cache } from "next/cache";
import { EmbeddedPost, PostNotFound } from "bsky-react-post";
import { fetchPost as _fetchPost } from "bsky-react-post/api";
import { cn } from "@/lib/utils";
// https://bsky-react-post.rhinobase.io/next
const fetchPost = cache(_fetchPost, undefined, {
revalidate: false, // cache indefinitely
tags: ["bluesky"],
});
const Bluesky = async ({
id,
handle,
className,
}: {
// https://github.com/rhinobase/react-bluesky/blob/97658fe636b92aaed78530505811d6de350f201e/packages/core/src/types/post.ts#L35
id: string;
handle: string;
className?: string;
}) => {
try {
const thread = await fetchPost({ id, handle });
return (
<div className={cn("min-h-[120px]", className)}>
{thread ? <EmbeddedPost thread={thread} /> : <PostNotFound />}
</div>
);
} catch (error) {
console.error(error);
return (
<div className={cn("min-h-[120px]", className)}>
<PostNotFound />
</div>
);
}
};
export default Bluesky;

View File

@ -1,3 +1,6 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const CodePen = ({
username,
id,
@ -5,6 +8,9 @@ const CodePen = ({
defaultTab = "html",
preview = true,
editable = false,
style,
className,
...rest
}: {
username: string;
id: string;
@ -12,7 +18,7 @@ const CodePen = ({
defaultTab?: string;
preview?: boolean;
editable?: boolean;
}) => {
} & ComponentPropsWithoutRef<"iframe">) => {
return (
<iframe
src={`https://codepen.io/${username}/embed/${id}/?${new URLSearchParams({
@ -20,7 +26,9 @@ const CodePen = ({
preview: `${!!preview}`,
editable: `${!!editable}`,
})}`}
style={{ height: `${height}px`, width: "100%", border: "0", overflow: "hidden" }}
style={{ height: `${height}px`, ...style }}
className={cn("w-full overflow-hidden border-none", className)}
{...rest}
/>
);
};

View File

@ -1,6 +1,13 @@
import Link from "@/components/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Gist = async ({ id, file }: { id: string; file?: string }) => {
const Gist = async ({
id,
file,
className,
...rest
}: { id: string; file?: string } & ComponentPropsWithoutRef<"iframe">) => {
const iframeId = `gist-${id}${file ? `-${file}` : ""}`;
const scriptUrl = `https://gist.github.com/${id}.js${file ? `?file=${file}` : ""}`;
@ -34,7 +41,8 @@ const Gist = async ({ id, file }: { id: string; file?: string }) => {
scrolling="no"
id={iframeId}
srcDoc={iframeHtml}
className="overflow-hidden border-none"
className={cn("overflow-hidden border-none", className)}
{...rest}
suppressHydrationWarning
/>
);

View File

@ -14,7 +14,7 @@ const Tweet = async ({ id, className }: { id: string; className?: string }) => {
const { data } = await fetchTweet(id);
return (
<div className={cn("min-h-[120px] [&_.react-tweet-theme]:![--tweet-container-margin:1.5rem_auto]", className)}>
<div className={cn("min-h-30 *:mx-auto! *:font-sans!", className)}>
{data ? (
<EmbeddedTweet
tweet={data}
@ -35,7 +35,7 @@ const Tweet = async ({ id, className }: { id: string; className?: string }) => {
console.error(error);
return (
<div className={cn("min-h-[120px] [&_.react-tweet-theme]:![--tweet-container-margin:1.5rem_auto]", className)}>
<div className={cn("min-h-30 *:mx-auto! *:font-sans!", className)}>
<TweetNotFound />
</div>
);

View File

@ -3,10 +3,15 @@
import YouTubeEmbed from "react-lite-youtube-embed";
import type { ComponentPropsWithoutRef } from "react";
import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
const YouTube = ({ ...rest }: Omit<ComponentPropsWithoutRef<typeof YouTubeEmbed>, "title">) => {
return <YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />;
return (
<div
// lite-youtube-embed CSS is imported in app/global.css to save a request
className="youtube-embed"
>
<YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />
</div>
);
};
export default YouTube;

View File

@ -3,7 +3,7 @@ import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
export const buttonVariants = cva(
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
@ -36,14 +36,14 @@ const Button = ({
variant,
size,
asChild = false,
...props
...rest
}: ComponentPropsWithoutRef<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) => {
const Comp = asChild ? Slot : "button";
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...rest} />;
};
export default Button;

20
components/ui/label.tsx Normal file
View File

@ -0,0 +1,20 @@
"use client";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Label = ({ className, ...rest }: ComponentPropsWithoutRef<typeof LabelPrimitive.Root>) => {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className
)}
{...rest}
/>
);
};
export default Label;

View File

@ -1,5 +1,2 @@
/** Path to directory with .mdx files, relative to project root. */
export const POSTS_DIR = "notes" as const;
/** Maximum width of content wrapper (e.g. for images) in pixels. */
export const MAX_WIDTH = 896 as const;

View File

@ -1,8 +1,12 @@
import { CodeIcon, HomeIcon, MailIcon, PencilLineIcon } from "lucide-react";
import MenuItem from "@/components/layout/menu-item";
import { ComponentPropsWithoutRef } from "react";
import { CodeIcon, HomeIcon, MailIcon, PencilLineIcon, type LucideIcon } from "lucide-react";
export const menuItems: Array<ComponentPropsWithoutRef<typeof MenuItem>> = [
export type MenuItemConfig = {
text?: string;
href?: `/${string}`;
icon?: LucideIcon;
};
export const menuItems: MenuItemConfig[] = [
{
text: "Home",
href: "/",

View File

@ -130,7 +130,7 @@ export const env = createEnv({
/**
* Optional. Consistent timezone for the site. Doesn't really matter what it is, as long as it's the same everywhere
* to avoid hydration complaints.
* to avoid hydration complaints. Defaults to `America/New_York`.
*
* @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/

View File

@ -1,7 +1,11 @@
// a weird system but makes it impossible to accidentally end up with multiple imports of the same font. see:
// https://nextjs.org/docs/pages/building-your-application/optimizing/fonts#reusing-fonts
import { Geist as GeistSansLoader, Geist_Mono as GeistMonoLoader } from "next/font/google";
import {
Geist as GeistSansLoader,
Geist_Mono as GeistMonoLoader,
Comic_Neue as ComicNeueLoader,
} from "next/font/google";
export const GeistSans = GeistSansLoader({
subsets: ["latin"],
@ -11,7 +15,6 @@ export const GeistSans = GeistSansLoader({
"system-ui",
"sans-serif",
],
variable: "--font-geist-sans",
preload: true,
});
@ -28,7 +31,14 @@ export const GeistMono = GeistMonoLoader({
"'Liberation Mono'",
"monospace",
],
adjustFontFallback: false,
variable: "--font-geist-mono",
preload: true,
});
export const ComicNeue = ComicNeueLoader({
weight: ["400", "700"],
style: ["normal", "italic"],
subsets: ["latin"],
display: "swap",
fallback: ["'Comic Sans MS'", "'Comic Sans'"],
preload: false,
});

View File

@ -4,9 +4,18 @@ import path from "path";
import fs from "fs/promises";
import glob from "fast-glob";
import { unified } from "unified";
import { remarkHtml, remarkParse, remarkSmartypants, remarkFrontmatter } from "@/lib/remark";
import {
remarkParse,
remarkSmartypants,
remarkFrontmatter,
remarkRehype,
remarkMdx,
remarkStripMdxImportsExports,
} from "@/lib/remark";
import { decode } from "html-entities";
import { POSTS_DIR } from "@/lib/config/constants";
import rehypeSanitize from "rehype-sanitize";
import rehypeStringify from "rehype-stringify";
export type FrontMatter = {
slug: string;
@ -58,12 +67,12 @@ export const getFrontMatter: {
const htmlTitle = await unified()
.use(remarkParse)
.use(remarkSmartypants)
.use(remarkHtml, {
sanitize: {
// allow *very* limited markdown to be used in post titles
tagNames: ["code", "em", "strong"],
},
.use(remarkRehype)
.use(rehypeSanitize, {
// allow *very* limited markdown to be used in post titles
tagNames: ["code", "em", "strong"],
})
.use(rehypeStringify)
.process(frontmatter.title)
.then((result) => result.toString().trim());
@ -108,35 +117,37 @@ export const getContent = cache(async (slug: string): Promise<string | undefined
try {
const content = await unified()
.use(remarkParse)
.use(remarkMdx)
.use(remarkStripMdxImportsExports)
.use(remarkFrontmatter)
.use(remarkSmartypants)
.use(remarkHtml, {
sanitize: {
tagNames: [
"p",
"a",
"em",
"strong",
"code",
"pre",
"blockquote",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"ul",
"ol",
"li",
"hr",
],
},
.use(remarkRehype)
.use(rehypeSanitize, {
tagNames: [
"p",
"a",
"em",
"strong",
"code",
"pre",
"blockquote",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"ul",
"ol",
"li",
"hr",
],
})
.use(rehypeStringify)
.process(await fs.readFile(path.join(process.cwd(), `${POSTS_DIR}/${slug}/index.mdx`)));
// convert the parsed content to a string with "safe" HTML
return content.toString().replaceAll("<p></p>\n", "").replaceAll("<p>{/* prettier-ignore */}</p>\n", "").trim();
return content.toString().replaceAll("/* prettier-ignore */", "").replaceAll("<p></p>", "").trim();
} catch (error) {
console.error(`Failed to load/parse content for post with slug "${slug}":`, error);
return undefined;

View File

@ -1,6 +1,7 @@
export { default as rehypeAutolinkHeadings } from "rehype-autolink-headings";
export { default as rehypeMdxCodeProps } from "rehype-mdx-code-props";
export { default as rehypeMdxImportMedia } from "rehype-mdx-import-media";
export { default as rehypeSanitize } from "rehype-sanitize";
export { default as rehypeSlug } from "rehype-slug";
export { default as rehypeStringify } from "rehype-stringify";
export { default as rehypeUnwrapImages } from "rehype-unwrap-images";
export { default as rehypeWrapper } from "rehype-wrapper";

View File

@ -1,6 +1,8 @@
export { default as remarkFrontmatter } from "remark-frontmatter";
export { default as remarkGfm } from "remark-gfm";
export { default as remarkHtml } from "remark-html";
export { default as remarkMdx } from "remark-mdx";
export { default as remarkMdxFrontmatter } from "remark-mdx-frontmatter";
export { default as remarkParse } from "remark-parse";
export { default as remarkRehype } from "remark-rehype";
export { default as remarkSmartypants } from "remark-smartypants";
export { default as remarkStripMdxImportsExports } from "remark-strip-mdx-imports-exports";

View File

@ -1,23 +1,21 @@
import { cn } from "./lib/utils";
import type { MDXComponents } from "mdx/types";
import NextImage from "next/image";
import Link from "@/components/link";
import CodeBlock from "@/components/code-block";
import Image from "@/components/image";
import HeadingAnchor from "@/components/heading-anchor";
import Video from "@/components/video";
import Bluesky from "@/components/third-party/bluesky";
import Tweet from "@/components/third-party/tweet";
import YouTube from "@/components/third-party/youtube";
import Gist from "@/components/third-party/gist";
import CodePen from "@/components/third-party/codepen";
import { cn } from "./lib/utils";
import type { MDXComponents } from "mdx/types";
export const useMDXComponents = (components: MDXComponents): MDXComponents => {
return {
...components,
// direct replacements for HTML tags generated by remark:
a: Link,
pre: CodeBlock,
pre: ({ className, ...rest }) => <CodeBlock className={cn("my-5 w-full font-mono text-sm", className)} {...rest} />,
code: ({ className, ...rest }) => (
// only applies to inline code, *not* highlighted code blocks!
<code
@ -26,48 +24,60 @@ export const useMDXComponents = (components: MDXComponents): MDXComponents => {
/>
),
img: ({ className, ...rest }) => (
// eslint-disable-next-line jsx-a11y/alt-text
<Image
<NextImage
className={cn(
"[&+em]:text-muted-foreground my-8 rounded-md [&+em]:-mt-4 [&+em]:block [&+em]:text-center [&+em]:text-[0.875em] [&+em]:leading-normal [&+em]:font-medium [&+em]:not-italic",
"mx-auto my-8 block h-auto max-w-full rounded-md",
"[&+em]:text-muted-foreground [&+em]:-mt-4 [&+em]:block [&+em]:text-center [&+em]:text-[0.875em] [&+em]:leading-normal [&+em]:font-medium [&+em]:not-italic",
className
)}
{...rest}
/>
),
figure: ({ className, ...rest }) => <figure className={cn("my-8 [&>*]:my-0", className)} {...rest} />,
figure: ({ className, ...rest }) => <figure className={cn("my-8 *:my-0", className)} {...rest} />,
figcaption: ({ className, ...rest }) => (
<figcaption className={cn("text-muted-foreground mt-3.5 text-[0.875em] leading-snug", className)} {...rest} />
),
blockquote: ({ className, ...rest }) => (
<blockquote className={cn("border-border text-muted-foreground mt-6 border-l-4 pl-4", className)} {...rest} />
),
h1: ({ className, ...rest }) => (
h1: ({ className, id, children, ...rest }) => (
<h1
className={cn(
"mt-6 mb-4 scroll-m-4 text-3xl leading-snug font-extrabold md:text-4xl [&_strong]:font-black [&+*]:mt-0",
"group mt-6 mb-4 scroll-m-4 text-3xl leading-snug font-extrabold md:text-4xl [&_strong]:font-black [&+*]:mt-0",
className
)}
id={id}
{...rest}
/>
>
{children}
{id && <HeadingAnchor id={id} title={children} className="opacity-0 group-hover:opacity-100 max-md:hidden" />}
</h1>
),
h2: ({ className, ...rest }) => (
h2: ({ className, id, children, ...rest }) => (
<h2
className={cn(
"mt-6 mb-4 scroll-m-4 text-xl leading-snug font-bold first:mt-0 md:text-2xl [&_code]:text-[0.875em] [&_strong]:font-extrabold [&+*]:mt-0",
"group mt-6 mb-4 scroll-m-4 text-xl leading-snug font-bold first:mt-0 md:text-2xl [&_code]:text-[0.875em] [&_strong]:font-extrabold [&+*]:mt-0",
className
)}
id={id}
{...rest}
/>
>
{children}
{id && <HeadingAnchor id={id} title={children} className="opacity-0 group-hover:opacity-100 max-md:hidden" />}
</h2>
),
h3: ({ className, ...rest }) => (
h3: ({ className, id, children, ...rest }) => (
<h3
className={cn(
"mt-6 mb-2.5 scroll-m-4 text-lg leading-relaxed font-semibold md:text-xl [&_code]:text-[0.9em] [&_strong]:font-bold [&+*]:mt-0",
"group mt-6 mb-2.5 scroll-m-4 text-lg leading-relaxed font-semibold md:text-xl [&_code]:text-[0.9em] [&_strong]:font-bold [&+*]:mt-0",
className
)}
id={id}
{...rest}
/>
>
{children}
{id && <HeadingAnchor id={id} title={children} className="opacity-0 group-hover:opacity-100 max-md:hidden" />}
</h3>
),
h4: ({ className, ...rest }) => (
<h4
@ -75,6 +85,8 @@ export const useMDXComponents = (components: MDXComponents): MDXComponents => {
{...rest}
/>
),
h5: ({ className, ...rest }) => <h5 className={cn("mt-6 mb-2 scroll-m-4 leading-normal", className)} {...rest} />,
h6: ({ className, ...rest }) => <h6 className={cn("mt-6 mb-2 scroll-m-4 leading-normal", className)} {...rest} />,
ul: ({ className, ...rest }) => <ul className={cn("my-5 list-disc pl-7 [&>li]:pl-1.5", className)} {...rest} />,
ol: ({ className, ...rest }) => <ol className={cn("my-5 list-decimal pl-7 [&>li]:pl-1.5", className)} {...rest} />,
li: ({ className, ...rest }) => (
@ -92,7 +104,6 @@ export const useMDXComponents = (components: MDXComponents): MDXComponents => {
// third-party embeds:
Video,
Bluesky,
Tweet,
YouTube,
Gist,

View File

@ -1,7 +1,5 @@
import * as remarkPlugins from "@/lib/remark";
import * as rehypePlugins from "@/lib/rehype";
import { fromHtml } from "hast-util-from-html";
import { toString as nodeToString } from "hast-util-to-string";
import type { NextConfig } from "next";
// check environment variables at build time
@ -38,12 +36,11 @@ const nextConfig = {
},
productionBrowserSourceMaps: true,
experimental: {
reactCompiler: true, // https://react.dev/learn/react-compiler
ppr: "incremental", // https://nextjs.org/docs/app/building-your-application/rendering/partial-prerendering#using-partial-prerendering
reactCompiler: true,
ppr: "incremental",
dynamicOnHover: true,
serverActions: {
// fix CSRF errors from tor reverse proxy
// https://nextjs.org/docs/app/building-your-application/deploying/multi-zones#server-actions
allowedOrigins: [
"jarv.is",
...(process.env.NEXT_PUBLIC_ONION_DOMAIN ? [process.env.NEXT_PUBLIC_ONION_DOMAIN] : []),
@ -164,30 +161,11 @@ const nextPlugins: Array<
rehypePlugins: [
rehypePlugins.rehypeUnwrapImages,
rehypePlugins.rehypeSlug,
[
rehypePlugins.rehypeAutolinkHeadings,
{
behavior: "append",
properties: {
ariaHidden: true,
className:
"text-muted-foreground hover:text-primary hover:no-underline ml-2 inline-block px-2 align-baseline max-md:hidden",
tabIndex: -1,
},
// @ts-ignore
content: (heading) =>
fromHtml(
`<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" className="inline-block align-baseline"><path fill-rule="evenodd" fill="currentcolor" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"/></svg><span class="sr-only">Jump to "${nodeToString(heading)}"</span>`,
{ fragment: true }
).children,
} satisfies Parameters<typeof rehypePlugins.rehypeAutolinkHeadings>[0],
],
[
rehypePlugins.rehypeWrapper,
{
className:
"text-[0.925rem] leading-relaxed first:mt-0 last:mb-0 md:text-base [&_p]:my-5 [&_strong]:font-bold",
} satisfies Parameters<typeof rehypePlugins.rehypeWrapper>[0],
className: "text-[0.925rem] leading-relaxed first:mt-0 last:mb-0 md:text-base [&_p]:my-5",
},
],
rehypePlugins.rehypeMdxCodeProps,
rehypePlugins.rehypeMdxImportMedia,

View File

@ -26,7 +26,7 @@ export const OctocatLink = ({ repo }) => {
xmlns="http://www.w3.org/2000/svg"
strokeWidth="0"
viewBox="0 0 24 24"
className="inline size-[24px] fill-current align-text-top"
className="inline size-6 fill-current align-text-top"
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</svg>

View File

@ -23,15 +23,15 @@
"@giscus/react": "^3.1.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/bundle-analyzer": "15.4.0-canary.22",
"@next/mdx": "15.4.0-canary.22",
"@next/bundle-analyzer": "15.4.0-canary.23",
"@next/mdx": "15.4.0-canary.23",
"@octokit/graphql": "^8.2.2",
"@octokit/graphql-schema": "^15.26.0",
"@radix-ui/react-slot": "^1.2.0",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-slot": "^1.2.2",
"@t3-oss/env-nextjs": "^0.13.4",
"@vercel/analytics": "^1.5.0",
"@vercel/kv": "^3.0.0",
"bsky-react-post": "^0.1.7",
"cheerio": "^1.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@ -40,11 +40,9 @@
"fast-glob": "^3.3.3",
"feed": "^4.2.2",
"geist": "^1.4.1",
"hast-util-from-html": "^2.0.3",
"hast-util-to-string": "^3.0.1",
"html-entities": "^2.6.0",
"lucide-react": "0.507.0",
"next": "15.4.0-canary.22",
"next": "15.4.0-canary.23",
"prop-types": "^15.8.1",
"react": "19.1.0",
"react-activity-calendar": "^2.7.11",
@ -54,22 +52,26 @@
"react-lite-youtube-embed": "^2.5.1",
"react-schemaorg": "^2.0.0",
"react-timeago": "^8.2.0",
"react-to-text": "^2.0.1",
"react-tooltip": "^5.28.1",
"react-turnstile": "^1.1.4",
"react-tweet": "^3.2.2",
"react-use": "^17.6.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-mdx-code-props": "^3.0.1",
"rehype-mdx-import-media": "^1.2.0",
"rehype-sanitize": "^6.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"rehype-unwrap-images": "^1.0.0",
"rehype-wrapper": "^1.0.1",
"rehype-wrapper": "^1.1.0",
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"remark-html": "^16.0.1",
"remark-mdx": "^3.1.0",
"remark-mdx-frontmatter": "^5.1.0",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"remark-smartypants": "^3.0.2",
"remark-strip-mdx-imports-exports": "^1.0.1",
"resend": "^4.5.1",
"server-only": "0.0.1",
"shiki": "^3.3.0",
@ -83,18 +85,16 @@
"@eslint/js": "^9.26.0",
"@jakejarvis/eslint-config": "^4.0.7",
"@tailwindcss/postcss": "^4.1.5",
"@types/hast": "^3.0.4",
"@types/mdx": "^2.0.13",
"@types/node": "^22.15.3",
"@types/node": "^22.15.10",
"@types/prop-types": "^15.7.14",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@types/react-is": "^19.0.0",
"@types/unist": "^3.0.3",
"babel-plugin-react-compiler": "19.0.0-beta-af1b7da-20250417",
"cross-env": "^7.0.3",
"eslint": "^9.26.0",
"eslint-config-next": "15.4.0-canary.22",
"eslint-config-next": "15.4.0-canary.23",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.31.0",
@ -116,7 +116,7 @@
"sharp": "^0.34.1"
},
"engines": {
"node": ">=20.x"
"node": ">=22.x"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
"cacheDirectories": [
@ -127,5 +127,12 @@
"*.{js,jsx,ts,tsx,md,mdx}": [
"eslint"
]
},
"pnpm": {
"peerDependencyRules": {
"allowedVersions": {
"react": "^19"
}
}
}
}

383
pnpm-lock.yaml generated
View File

@ -24,32 +24,32 @@ importers:
specifier: ^3.1.0
version: 3.1.0(@types/react@19.1.2)(react@19.1.0)
'@next/bundle-analyzer':
specifier: 15.4.0-canary.22
version: 15.4.0-canary.22
specifier: 15.4.0-canary.23
version: 15.4.0-canary.23
'@next/mdx':
specifier: 15.4.0-canary.22
version: 15.4.0-canary.22(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.2)(react@19.1.0))
specifier: 15.4.0-canary.23
version: 15.4.0-canary.23(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.2)(react@19.1.0))
'@octokit/graphql':
specifier: ^8.2.2
version: 8.2.2
'@octokit/graphql-schema':
specifier: ^15.26.0
version: 15.26.0
'@radix-ui/react-label':
specifier: ^2.1.6
version: 2.1.6(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@radix-ui/react-slot':
specifier: ^1.2.0
version: 1.2.0(@types/react@19.1.2)(react@19.1.0)
specifier: ^1.2.2
version: 1.2.2(@types/react@19.1.2)(react@19.1.0)
'@t3-oss/env-nextjs':
specifier: ^0.13.4
version: 0.13.4(arktype@2.1.20)(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.3)
'@vercel/analytics':
specifier: ^1.5.0
version: 1.5.0(next@15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
version: 1.5.0(next@15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
'@vercel/kv':
specifier: ^3.0.0
version: 3.0.0
bsky-react-post:
specifier: ^0.1.7
version: 0.1.7(react@19.1.0)(swr@2.3.3(react@19.1.0))
cheerio:
specifier: ^1.0.0
version: 1.0.0
@ -73,13 +73,7 @@ importers:
version: 4.2.2
geist:
specifier: ^1.4.1
version: 1.4.1(next@15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
hast-util-from-html:
specifier: ^2.0.3
version: 2.0.3
hast-util-to-string:
specifier: ^3.0.1
version: 3.0.1
version: 1.4.1(next@15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
html-entities:
specifier: ^2.6.0
version: 2.6.0
@ -87,8 +81,8 @@ importers:
specifier: 0.507.0
version: 0.507.0(react@19.1.0)
next:
specifier: 15.4.0-canary.22
version: 15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
specifier: 15.4.0-canary.23
version: 15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
prop-types:
specifier: ^15.8.1
version: 15.8.1
@ -116,6 +110,9 @@ importers:
react-timeago:
specifier: ^8.2.0
version: 8.2.0(react@19.1.0)
react-to-text:
specifier: ^2.0.1
version: 2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-tooltip:
specifier: ^5.28.1
version: 5.28.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -128,42 +125,51 @@ importers:
react-use:
specifier: ^17.6.0
version: 17.6.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
rehype-autolink-headings:
specifier: ^7.1.0
version: 7.1.0
rehype-mdx-code-props:
specifier: ^3.0.1
version: 3.0.1
rehype-mdx-import-media:
specifier: ^1.2.0
version: 1.2.0
rehype-sanitize:
specifier: ^6.0.0
version: 6.0.0
rehype-slug:
specifier: ^6.0.0
version: 6.0.0
rehype-stringify:
specifier: ^10.0.1
version: 10.0.1
rehype-unwrap-images:
specifier: ^1.0.0
version: 1.0.0
rehype-wrapper:
specifier: ^1.0.1
version: 1.0.1
specifier: ^1.1.0
version: 1.1.0
remark-frontmatter:
specifier: ^5.0.0
version: 5.0.0
remark-gfm:
specifier: ^4.0.1
version: 4.0.1
remark-html:
specifier: ^16.0.1
version: 16.0.1
remark-mdx:
specifier: ^3.1.0
version: 3.1.0
remark-mdx-frontmatter:
specifier: ^5.1.0
version: 5.1.0
remark-parse:
specifier: ^11.0.0
version: 11.0.0
remark-rehype:
specifier: ^11.1.2
version: 11.1.2
remark-smartypants:
specifier: ^3.0.2
version: 3.0.2
remark-strip-mdx-imports-exports:
specifier: ^1.0.1
version: 1.0.1
resend:
specifier: ^4.5.1
version: 4.5.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -198,15 +204,12 @@ importers:
'@tailwindcss/postcss':
specifier: ^4.1.5
version: 4.1.5
'@types/hast':
specifier: ^3.0.4
version: 3.0.4
'@types/mdx':
specifier: ^2.0.13
version: 2.0.13
'@types/node':
specifier: ^22.15.3
version: 22.15.3
specifier: ^22.15.10
version: 22.15.10
'@types/prop-types':
specifier: ^15.7.14
version: 15.7.14
@ -219,9 +222,6 @@ importers:
'@types/react-is':
specifier: ^19.0.0
version: 19.0.0
'@types/unist':
specifier: ^3.0.3
version: 3.0.3
babel-plugin-react-compiler:
specifier: 19.0.0-beta-af1b7da-20250417
version: 19.0.0-beta-af1b7da-20250417
@ -232,8 +232,8 @@ importers:
specifier: ^9.26.0
version: 9.26.0(jiti@2.4.2)
eslint-config-next:
specifier: 15.4.0-canary.22
version: 15.4.0-canary.22(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
specifier: 15.4.0-canary.23
version: 15.4.0-canary.23(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
eslint-config-prettier:
specifier: ^10.1.2
version: 10.1.2(eslint@9.26.0(jiti@2.4.2))
@ -668,17 +668,17 @@ packages:
'@napi-rs/wasm-runtime@0.2.9':
resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
'@next/bundle-analyzer@15.4.0-canary.22':
resolution: {integrity: sha512-nOCxcXutCNp2S7pdCh1u/HwPpII1Y2KyYlZMJW9LB0Z3u88oItIYErA+gDBog2dIBpKKRZ5/RMOK23g6GfSLsw==}
'@next/bundle-analyzer@15.4.0-canary.23':
resolution: {integrity: sha512-aIZtD1ezOnez6pPpbm5UUKI7fBaXwUu+vQlY8HZptkY89cgD/4908K5IK6sQTGGDcEH0Kp2HvHtVsjAPNfcFvw==}
'@next/env@15.4.0-canary.22':
resolution: {integrity: sha512-mpIGukp4WmACk21wHnfDH/UEIQTh9pNeXj8CTZwfMMo4DzaU5TBtyF5OxE65oz93ZzPetSOXHc71Ka6oTn8luA==}
'@next/env@15.4.0-canary.23':
resolution: {integrity: sha512-bv6Fbe265CnGo0P8Uay9LzL4sziJh6uc7dfNt7CJIhsl8rzJOJahyucTtUvgcUXBX2hklsTJVuHzAVpb8wOZ8w==}
'@next/eslint-plugin-next@15.4.0-canary.22':
resolution: {integrity: sha512-Bl4tYIaOSTIOgSHNyFzhXZ4oPF4/+7NiJ3Xh/syMNmG1pTjIxNwJaKE86E3aufQNJhVrThvxgPDzRbEdQ6Y92A==}
'@next/eslint-plugin-next@15.4.0-canary.23':
resolution: {integrity: sha512-zII8MZ/di2GKBwBerIuYlCYCC4jUoawpD+e4SfbGJQGCY3R9kPeXT9aa2GmhzgrW+wn/125H7RQNS3KPNtL1fA==}
'@next/mdx@15.4.0-canary.22':
resolution: {integrity: sha512-F91AWBzHMXu6aa2CZL5+mDgutD7DPGIL5F1os95ox0Ep97DuOQpofDUh8elxQgOr+xrBZu9SJWz3lRTR+u8K9g==}
'@next/mdx@15.4.0-canary.23':
resolution: {integrity: sha512-C07qnjppPpD432/o2BCA19BqO9Q3cq/Cvgd/LTz+8uO5jBvQRYf+42ZuiclwnWcPZQucG59k4LDMssL5ludkvw==}
peerDependencies:
'@mdx-js/loader': '>=0.15.0'
'@mdx-js/react': '>=0.15.0'
@ -688,50 +688,50 @@ packages:
'@mdx-js/react':
optional: true
'@next/swc-darwin-arm64@15.4.0-canary.22':
resolution: {integrity: sha512-AHyhMTbC+GaNL3rQkhEFK0tGaFWgmAWjKmAKQv3gTsmw6CL8NwKwvVm1BnXtYGwRWDNueqeW7P1swMPPo5+kaQ==}
'@next/swc-darwin-arm64@15.4.0-canary.23':
resolution: {integrity: sha512-ZaGUTvGQEQUhxBj0Qu2uoxStA8yf/TMA0lrs2mVrQ3nqSZh7ytInYuY8JtojpKZqA2eTGhUciUXbu0QdjOuHgA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.4.0-canary.22':
resolution: {integrity: sha512-55I0LetXfNEc3EWXYbIe7TTb0D5PNEe1p/gN+2R1jNbLeNiJvQg68B87W6NNHCb3NqlifZHcM48KECOlPL54+Q==}
'@next/swc-darwin-x64@15.4.0-canary.23':
resolution: {integrity: sha512-visdkQByAry6a/C7K9l9lb51XEejlm2zmih8SqkkIIGLnQ2j5/c1pwWTmbWp/LumsV2YLTATTpC31xA+SemDSg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.4.0-canary.22':
resolution: {integrity: sha512-MgJxjASNYfode39h+s7lizYOpkeqlhMUSwAOmuIx45PwlhNhUUrIntIrY54DMPJfyH1VE63kLN+I916dHuxxdw==}
'@next/swc-linux-arm64-gnu@15.4.0-canary.23':
resolution: {integrity: sha512-txwv3qVj/VqnoKpe0jyrFoy0Hl2zl2MPslkMjLth9qHPIAOCms/iI8IJvY4ARgbeLTIHPNKzKptdYiTJKBiUjA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.4.0-canary.22':
resolution: {integrity: sha512-nSepBTirHUrnMDYyXn5f9sKKlLymxiBndfEs8tjcGehwnSWzOWhMfPvcjEHW0TimPmu8+TzdMtQ0SzfKMqeHtg==}
'@next/swc-linux-arm64-musl@15.4.0-canary.23':
resolution: {integrity: sha512-a0IWkOuiOdZU++yN6vgqZOFeh9t8/pW+7fQSQktWWob6xLCYVh0Ib0OCGPwolh7CxY29JHSoORrpRTg/rhD0iA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.4.0-canary.22':
resolution: {integrity: sha512-uWX6XN6IKwklI+PlgVMMEKlYg20Lk0uXbba2ulden+p9rcykEdtx8Ef072F3gsm4FpB2qBQuZk5FkGpxAUxyhw==}
'@next/swc-linux-x64-gnu@15.4.0-canary.23':
resolution: {integrity: sha512-wDibCQ4qOg74/ZHDEIk3yRD1vIm9ns3tn+92BS6OXnMNAUGaRXP7hQwUjmnVj/TiqCoMyNfRVvjYjwkAOROa/w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.4.0-canary.22':
resolution: {integrity: sha512-NSjCEl7VjRdB7S3/hA6t13Z3bpQF6HAikDxS66QXBmHkcwgODLCitnhJ+5tkT3tpce9t9VLmEYt+/T3D0JKEWg==}
'@next/swc-linux-x64-musl@15.4.0-canary.23':
resolution: {integrity: sha512-AGE1pu3WinXYqPdZ3a1t9IvsJ4Sy/AFeDDf3fvLKAFJkxixhA6BuOtzFDyphuGQ30RyhrOAZti9vetwsmiwPrQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.4.0-canary.22':
resolution: {integrity: sha512-PXCD2O5jYEzScUZ2xLfVrs+eSUmImtZD/kEXbRoEvLsP14oiT0PiALkpmqfJ80V3FuGoUa3kPXji1RFV/aLYoQ==}
'@next/swc-win32-arm64-msvc@15.4.0-canary.23':
resolution: {integrity: sha512-GbzYRcjcN8z2v2ddANtqOdBk++fwQ/DZ408y184mtEupq0L7LQMtDQCrxAA2EsLOEHUJEzgjcTrtV6ViwA5BMQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.4.0-canary.22':
resolution: {integrity: sha512-J92SZtE6GHynXesz+dwlP7yOgh1P7xgVQZp5U7vOSiqT1e9wpM6q9UMJ7uGvSrxF4wf/P1Ta3alsCOYmhhgnwg==}
'@next/swc-win32-x64-msvc@15.4.0-canary.23':
resolution: {integrity: sha512-a0rFUokWcYMVQjdql5hw1C6aDGobCLqtLls/U1JSHmCET7a/WNpp/sTrqp+cnlSR4wZyb/Eo/tCDmwkiYYcTVw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -821,8 +821,34 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-slot@1.2.0':
resolution: {integrity: sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==}
'@radix-ui/react-label@2.1.6':
resolution: {integrity: sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-primitive@2.1.2':
resolution: {integrity: sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.2':
resolution: {integrity: sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
@ -1032,8 +1058,8 @@ packages:
'@types/nlcst@2.0.3':
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
'@types/node@22.15.3':
resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==}
'@types/node@22.15.10':
resolution: {integrity: sha512-j2U4KRlgZ9Q8tVO/KDAvXu68vutX4kxoRysL6Q22oEU4ZFT2A16aIyqiIWAwFBZkvKep2UOcSGNoLe/6BI0nrg==}
'@types/prop-types@15.7.14':
resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==}
@ -1371,12 +1397,6 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
bsky-react-post@0.1.7:
resolution: {integrity: sha512-c+M94RCQQmEHKaB93tAJ42SnrL6nKHlaI4qRRKMy0+Lqo0osksKaW+VNya5iQXihp1Mx59zM1WEVdd9OyHrteQ==}
peerDependencies:
react: '>= 18.0.0'
swr: ^2.2.5
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@ -1755,8 +1775,8 @@ packages:
resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
engines: {node: '>=12'}
eslint-config-next@15.4.0-canary.22:
resolution: {integrity: sha512-frZoXDGP9o/K87kZlf7R9BV0mzcjT6mWq57j7u9R8fM0GH8bMTw46D2X7oLFZXSD9pWeGPt1DgjDiTpxHSKmLQ==}
eslint-config-next@15.4.0-canary.23:
resolution: {integrity: sha512-KrvolI+smKN05kLh/hX8oLGyQ68nciD1m/WEzMtqVPXZTdQRFp6Dr2w3zoUTW8/2ZEcoTTl6KkaFPrz1s+zOFQ==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0 || ^9.0.0
typescript: '>=3.3.1'
@ -2196,12 +2216,6 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hast-util-from-html@2.0.3:
resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
hast-util-has-property@3.0.0:
resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==}
@ -2211,12 +2225,6 @@ packages:
hast-util-interactive@3.0.0:
resolution: {integrity: sha512-9VFa3kP6AT40BNYcPmn3jpsG+1KPDF0rUFCrFVQDUsuUXZ3YLODm8UGV0tmYzFpcOIQXTAOi2ccS3ywlj2dQTA==}
hast-util-is-element@3.0.0:
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
hast-util-parse-selector@4.0.0:
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
hast-util-properties-to-mdx-jsx-attributes@1.0.1:
resolution: {integrity: sha512-ZzxhjHZ+gyxaPIFp/nuRpVL4GIFoqzfH6vNgjaA3CuUAV6XCxYwAQfRczrZRkgL6msi6DdOl+/QEduOdzszvbg==}
@ -2238,9 +2246,6 @@ packages:
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
hermes-estree@0.25.1:
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
@ -2936,8 +2941,8 @@ packages:
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
engines: {node: '>= 0.6'}
next@15.4.0-canary.22:
resolution: {integrity: sha512-wM7izrMVsjimEDswVq/++qFnCy03nwfLpNC/El+LJu02vf+dVNU2FoPF5ni5AsUQstYLAqCrqnE82YQJGNDBTg==}
next@15.4.0-canary.23:
resolution: {integrity: sha512-iL+q0cJpB/rHDbkbuuVOb58FV3bgneioJK7Q3YF2p4wOXXb0xjX29puqO91fYCLeQpqqfaxB/T7ftLSxQz02KA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
@ -3318,6 +3323,12 @@ packages:
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-to-text@2.0.1:
resolution: {integrity: sha512-4lGuxY+/PqASinxrv7/ydE8wGdnZCGsTNIFiO6DKykzy703jI5pc0/fyyB6BNrBWmtYJ7H9dGY80PJ3V1/8UIg==}
peerDependencies:
react: ^18.2.0
react-dom: ^18.2.0
react-tooltip@5.28.1:
resolution: {integrity: sha512-ZA4oHwoIIK09TS7PvSLFcRlje1wGZaxw6xHvfrzn6T82UcMEfEmHVCad16Gnr4NDNDh93HyN037VK4HDi5odfQ==}
peerDependencies:
@ -3389,9 +3400,6 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
rehype-autolink-headings@7.1.0:
resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==}
rehype-mdx-code-props@3.0.1:
resolution: {integrity: sha512-BWWKn0N6r7/qd7lbLgv5J8of7imz1l1PyCNoY7BH0AOR9JdJlQIfA9cKqTZVEb2h2GPKh473qrBajF0i01fq3A==}
@ -3401,14 +3409,20 @@ packages:
rehype-recma@1.0.0:
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
rehype-sanitize@6.0.0:
resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==}
rehype-slug@6.0.0:
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
rehype-stringify@10.0.1:
resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
rehype-unwrap-images@1.0.0:
resolution: {integrity: sha512-wzW5Mk9IlVF2UwXC5NtIZsx1aHYbV8+bLWjJnlZaaamz5QU52RppWtq1uEZJqGo8d9Y4RuDqidB6r9RFpKugIg==}
rehype-wrapper@1.0.1:
resolution: {integrity: sha512-YYjm7xwqC9ULuuM0Gtx4CaE3D2Pwhy/mCMNGU6CbMbXMcseF0QxzvdQ79IemkWAwJFbRbR5YXEkGv8qwDupUCQ==}
rehype-wrapper@1.1.0:
resolution: {integrity: sha512-mb0wIW/vASR21okOsjQJikEx9JYEGGCNqXaHfgcvXHyD5nMZBgxlmHXlr/HUXAvjNu9p+wC0sXg9P/ydaMlBCQ==}
remark-frontmatter@5.0.0:
resolution: {integrity: sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==}
@ -3416,9 +3430,6 @@ packages:
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
remark-html@16.0.1:
resolution: {integrity: sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==}
remark-mdx-frontmatter@5.1.0:
resolution: {integrity: sha512-F2l+FydK/QVwYMC4niMYl4Kh83TIfoR4qV9ekh/riWRakTTyjcLLyKTBo9fVgEtOmTEfIrqWwiYIm42+I5PMfQ==}
@ -3438,6 +3449,9 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
remark-strip-mdx-imports-exports@1.0.1:
resolution: {integrity: sha512-N6YR9Tnib79WUg6Xjy1YRQ3q5/3SLsl8jCyyFSLRbERdL8KHLnWiWhLEzfKw5z/037iWs9sBr9UK7YDgagzzeA==}
resend@4.5.1:
resolution: {integrity: sha512-ryhHpZqCBmuVyzM19IO8Egtc2hkWI4JOL5lf5F3P7Dydu3rFeX6lHNpGqG0tjWoZ63rw0l731JEmuJZBdDm3og==}
engines: {node: '>=18'}
@ -3978,9 +3992,6 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
vfile-message@4.0.2:
resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
@ -3999,9 +4010,6 @@ packages:
walk-up-path@3.0.1:
resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==}
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
webpack-bundle-analyzer@4.10.1:
resolution: {integrity: sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==}
engines: {node: '>= 10.13.0'}
@ -4550,48 +4558,48 @@ snapshots:
'@tybys/wasm-util': 0.9.0
optional: true
'@next/bundle-analyzer@15.4.0-canary.22':
'@next/bundle-analyzer@15.4.0-canary.23':
dependencies:
webpack-bundle-analyzer: 4.10.1
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@next/env@15.4.0-canary.22': {}
'@next/env@15.4.0-canary.23': {}
'@next/eslint-plugin-next@15.4.0-canary.22':
'@next/eslint-plugin-next@15.4.0-canary.23':
dependencies:
fast-glob: 3.3.1
'@next/mdx@15.4.0-canary.22(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.2)(react@19.1.0))':
'@next/mdx@15.4.0-canary.23(@mdx-js/loader@3.1.0(acorn@8.14.1))(@mdx-js/react@3.1.0(@types/react@19.1.2)(react@19.1.0))':
dependencies:
source-map: 0.7.4
optionalDependencies:
'@mdx-js/loader': 3.1.0(acorn@8.14.1)
'@mdx-js/react': 3.1.0(@types/react@19.1.2)(react@19.1.0)
'@next/swc-darwin-arm64@15.4.0-canary.22':
'@next/swc-darwin-arm64@15.4.0-canary.23':
optional: true
'@next/swc-darwin-x64@15.4.0-canary.22':
'@next/swc-darwin-x64@15.4.0-canary.23':
optional: true
'@next/swc-linux-arm64-gnu@15.4.0-canary.22':
'@next/swc-linux-arm64-gnu@15.4.0-canary.23':
optional: true
'@next/swc-linux-arm64-musl@15.4.0-canary.22':
'@next/swc-linux-arm64-musl@15.4.0-canary.23':
optional: true
'@next/swc-linux-x64-gnu@15.4.0-canary.22':
'@next/swc-linux-x64-gnu@15.4.0-canary.23':
optional: true
'@next/swc-linux-x64-musl@15.4.0-canary.22':
'@next/swc-linux-x64-musl@15.4.0-canary.23':
optional: true
'@next/swc-win32-arm64-msvc@15.4.0-canary.22':
'@next/swc-win32-arm64-msvc@15.4.0-canary.23':
optional: true
'@next/swc-win32-x64-msvc@15.4.0-canary.22':
'@next/swc-win32-x64-msvc@15.4.0-canary.23':
optional: true
'@nodelib/fs.scandir@2.1.5':
@ -4707,7 +4715,25 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.2
'@radix-ui/react-slot@1.2.0(@types/react@19.1.2)(react@19.1.0)':
'@radix-ui/react-label@2.1.6(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
'@types/react-dom': 19.1.3(@types/react@19.1.2)
'@radix-ui/react-primitive@2.1.2(@types/react-dom@19.1.3(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@radix-ui/react-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
optionalDependencies:
'@types/react': 19.1.2
'@types/react-dom': 19.1.3(@types/react@19.1.2)
'@radix-ui/react-slot@1.2.2(@types/react@19.1.2)(react@19.1.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0)
react: 19.1.0
@ -4863,7 +4889,7 @@ snapshots:
'@types/concat-stream@2.0.3':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.10
'@types/debug@4.1.12':
dependencies:
@ -4899,7 +4925,7 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
'@types/node@22.15.3':
'@types/node@22.15.10':
dependencies:
undici-types: 6.21.0
@ -5058,9 +5084,9 @@ snapshots:
dependencies:
crypto-js: 4.2.0
'@vercel/analytics@1.5.0(next@15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
'@vercel/analytics@1.5.0(next@15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
optionalDependencies:
next: 15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next: 15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react: 19.1.0
'@vercel/kv@3.0.0':
@ -5241,11 +5267,6 @@ snapshots:
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
bsky-react-post@0.1.7(react@19.1.0)(swr@2.3.3(react@19.1.0)):
dependencies:
react: 19.1.0
swr: 2.3.3(react@19.1.0)
buffer-from@1.1.2: {}
bytes@3.1.2: {}
@ -5679,9 +5700,9 @@ snapshots:
escape-string-regexp@5.0.0: {}
eslint-config-next@15.4.0-canary.22(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3):
eslint-config-next@15.4.0-canary.23(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3):
dependencies:
'@next/eslint-plugin-next': 15.4.0-canary.22
'@next/eslint-plugin-next': 15.4.0-canary.23
'@rushstack/eslint-patch': 1.11.0
'@typescript-eslint/eslint-plugin': 8.30.1(@typescript-eslint/parser@8.30.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
'@typescript-eslint/parser': 8.30.1(eslint@9.26.0(jiti@2.4.2))(typescript@5.8.3)
@ -6153,9 +6174,9 @@ snapshots:
functions-have-names@1.2.3: {}
geist@1.4.1(next@15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)):
geist@1.4.1(next@15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)):
dependencies:
next: 15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
next: 15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
gensync@1.0.0-beta.2: {}
@ -6266,26 +6287,6 @@ snapshots:
dependencies:
function-bind: 1.1.2
hast-util-from-html@2.0.3:
dependencies:
'@types/hast': 3.0.4
devlop: 1.1.0
hast-util-from-parse5: 8.0.3
parse5: 7.2.1
vfile: 6.0.3
vfile-message: 4.0.2
hast-util-from-parse5@8.0.3:
dependencies:
'@types/hast': 3.0.4
'@types/unist': 3.0.3
devlop: 1.1.0
hastscript: 9.0.1
property-information: 7.0.0
vfile: 6.0.3
vfile-location: 5.0.3
web-namespaces: 2.0.1
hast-util-has-property@3.0.0:
dependencies:
'@types/hast': 3.0.4
@ -6299,14 +6300,6 @@ snapshots:
'@types/hast': 3.0.4
hast-util-has-property: 3.0.0
hast-util-is-element@3.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-parse-selector@4.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-properties-to-mdx-jsx-attributes@1.0.1:
dependencies:
'@types/estree': 1.0.7
@ -6389,14 +6382,6 @@ snapshots:
dependencies:
'@types/hast': 3.0.4
hastscript@9.0.1:
dependencies:
'@types/hast': 3.0.4
comma-separated-tokens: 2.0.3
hast-util-parse-selector: 4.0.0
property-information: 7.0.0
space-separated-tokens: 2.0.2
hermes-estree@0.25.1: {}
hermes-parser@0.25.1:
@ -7341,9 +7326,9 @@ snapshots:
negotiator@1.0.0: {}
next@15.4.0-canary.22(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
next@15.4.0-canary.23(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-af1b7da-20250417)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@next/env': 15.4.0-canary.22
'@next/env': 15.4.0-canary.23
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001714
postcss: 8.4.31
@ -7351,14 +7336,14 @@ snapshots:
react-dom: 19.1.0(react@19.1.0)
styled-jsx: 5.1.6(@babel/core@7.26.10)(react@19.1.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.4.0-canary.22
'@next/swc-darwin-x64': 15.4.0-canary.22
'@next/swc-linux-arm64-gnu': 15.4.0-canary.22
'@next/swc-linux-arm64-musl': 15.4.0-canary.22
'@next/swc-linux-x64-gnu': 15.4.0-canary.22
'@next/swc-linux-x64-musl': 15.4.0-canary.22
'@next/swc-win32-arm64-msvc': 15.4.0-canary.22
'@next/swc-win32-x64-msvc': 15.4.0-canary.22
'@next/swc-darwin-arm64': 15.4.0-canary.23
'@next/swc-darwin-x64': 15.4.0-canary.23
'@next/swc-linux-arm64-gnu': 15.4.0-canary.23
'@next/swc-linux-arm64-musl': 15.4.0-canary.23
'@next/swc-linux-x64-gnu': 15.4.0-canary.23
'@next/swc-linux-x64-musl': 15.4.0-canary.23
'@next/swc-win32-arm64-msvc': 15.4.0-canary.23
'@next/swc-win32-x64-msvc': 15.4.0-canary.23
babel-plugin-react-compiler: 19.0.0-beta-af1b7da-20250417
sharp: 0.34.1
transitivePeerDependencies:
@ -7686,6 +7671,11 @@ snapshots:
dependencies:
react: 19.1.0
react-to-text@2.0.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
react-tooltip@5.28.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@floating-ui/dom': 1.6.13
@ -7803,15 +7793,6 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
rehype-autolink-headings@7.1.0:
dependencies:
'@types/hast': 3.0.4
'@ungap/structured-clone': 1.3.0
hast-util-heading-rank: 3.0.0
hast-util-is-element: 3.0.0
unified: 11.0.5
unist-util-visit: 5.0.0
rehype-mdx-code-props@3.0.1:
dependencies:
'@types/hast': 3.0.4
@ -7842,6 +7823,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
rehype-sanitize@6.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-sanitize: 5.0.2
rehype-slug@6.0.0:
dependencies:
'@types/hast': 3.0.4
@ -7850,6 +7836,12 @@ snapshots:
hast-util-to-string: 3.0.1
unist-util-visit: 5.0.0
rehype-stringify@10.0.1:
dependencies:
'@types/hast': 3.0.4
hast-util-to-html: 9.0.5
unified: 11.0.5
rehype-unwrap-images@1.0.0:
dependencies:
'@types/hast': 3.0.4
@ -7857,7 +7849,7 @@ snapshots:
hast-util-whitespace: 3.0.0
unist-util-visit: 5.0.0
rehype-wrapper@1.0.1: {}
rehype-wrapper@1.1.0: {}
remark-frontmatter@5.0.0:
dependencies:
@ -7879,14 +7871,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
remark-html@16.0.1:
dependencies:
'@types/mdast': 4.0.4
hast-util-sanitize: 5.0.2
hast-util-to-html: 9.0.5
mdast-util-to-hast: 13.2.0
unified: 11.0.5
remark-mdx-frontmatter@5.1.0:
dependencies:
'@types/mdast': 4.0.4
@ -7933,6 +7917,8 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
remark-strip-mdx-imports-exports@1.0.1: {}
resend@4.5.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@react-email/render': 1.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -8493,7 +8479,7 @@ snapshots:
'@types/concat-stream': 2.0.3
'@types/debug': 4.1.12
'@types/is-empty': 1.2.3
'@types/node': 22.15.3
'@types/node': 22.15.10
'@types/unist': 3.0.3
concat-stream: 2.0.0
debug: 4.4.0
@ -8634,11 +8620,6 @@ snapshots:
vary@1.1.2: {}
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3
vfile: 6.0.3
vfile-message@4.0.2:
dependencies:
'@types/unist': 3.0.3
@ -8672,8 +8653,6 @@ snapshots:
walk-up-path@3.0.1: {}
web-namespaces@2.0.1: {}
webpack-bundle-analyzer@4.10.1:
dependencies:
'@discoveryjs/json-ext': 0.5.7

View File

@ -34,10 +34,8 @@
"remark-*",
"unified",
"unist-*",
"hast-*",
"@types/mdx",
"@types/unist",
"@types/hast"
"@types/unist"
],
"groupName": "mdx"
},