1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 12:58:28 -04:00

a bunch of cleanup, mostly of my messy css hacks over the years

This commit is contained in:
Jake Jarvis 2025-04-18 12:11:16 -04:00
parent eec8f5e1c2
commit 98ea88dae9
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
25 changed files with 221 additions and 184 deletions

View File

@ -15,6 +15,14 @@ export const metadata = addMetadata({
alternates: { alternates: {
canonical: "/birthday", canonical: "/birthday",
}, },
openGraph: {
videos: [
{
url: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
type: "video/webm",
},
],
},
}); });
const Page = () => { const Page = () => {

View File

@ -37,7 +37,8 @@
} }
.submitButton { .submitButton {
flex-shrink: 0; display: flex;
align-items: center;
height: 3.25em; height: 3.25em;
padding: 1em 1.25em; padding: 1em 1.25em;
margin-right: 1.5em; margin-right: 1.5em;
@ -56,9 +57,16 @@
background-color: var(--colors-link); background-color: var(--colors-link);
} }
.submitIcon {
width: 1.3em;
height: 1.3em;
margin-right: 0.5em;
}
.result { .result {
display: flex;
align-items: center;
font-weight: 600; font-weight: 600;
line-height: 1.5;
} }
.result.success { .result.success {
@ -70,8 +78,7 @@
} }
.resultIcon { .resultIcon {
display: inline;
width: 1.3em; width: 1.3em;
height: 1.3em; height: 1.3em;
vertical-align: -0.3em; margin-right: 0.25em;
} }

View File

@ -5,7 +5,7 @@ import { useActionState, useState } from "react";
import TextareaAutosize from "react-textarea-autosize"; import TextareaAutosize from "react-textarea-autosize";
import Turnstile from "react-turnstile"; import Turnstile from "react-turnstile";
import clsx from "clsx"; import clsx from "clsx";
import { CheckIcon, XIcon } from "lucide-react"; import { SendIcon, LoaderIcon, CheckIcon, XIcon } from "lucide-react";
import Link from "../../components/Link"; import Link from "../../components/Link";
import { send, type ContactState, type ContactInput } from "./action"; import { send, type ContactState, type ContactInput } from "./action";
@ -80,13 +80,12 @@ const ContactForm = () => {
stroke="currentColor" stroke="currentColor"
strokeWidth="0" strokeWidth="0"
viewBox="0 0 24 24" viewBox="0 0 24 24"
height="1.25em"
width="1.25em" width="1.25em"
height="1.25em"
style={{ style={{
display: "inline",
width: "1.25em", width: "1.25em",
height: "1.25em", height: "1.25em",
verticalAlign: "-0.25em", verticalAlign: "text-top",
marginRight: "0.25em", marginRight: "0.25em",
}} }}
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -115,19 +114,12 @@ const ContactForm = () => {
{!formState.success && ( {!formState.success && (
<button type="submit" disabled={pending} className={styles.submitButton}> <button type="submit" disabled={pending} className={styles.submitButton}>
{pending ? ( {pending ? (
<span>Sending...</span> <>
<LoaderIcon size="1.3em" className={styles.submitIcon} /> <span>Sending...</span>
</>
) : ( ) : (
<> <>
<span <SendIcon size="1.3em" className={styles.submitIcon} /> <span>Send</span>
style={{
fontSize: "1.3em",
marginRight: "0.3em",
lineHeight: "1",
}}
>
📤
</span>{" "}
<span>Send</span>
</> </>
)} )}
</button> </button>

View File

@ -1,3 +1,4 @@
import { LockIcon } from "lucide-react";
import PageTitle from "../../components/PageTitle"; import PageTitle from "../../components/PageTitle";
import Link from "../../components/Link"; import Link from "../../components/Link";
import { addMetadata } from "../../lib/helpers/metadata"; import { addMetadata } from "../../lib/helpers/metadata";
@ -28,7 +29,15 @@ const Page = () => {
<Link href="https://fediverse.jarv.is/@jake">direct message on Mastodon</Link>. <Link href="https://fediverse.jarv.is/@jake">direct message on Mastodon</Link>.
</p> </p>
<p> <p>
🔐 You can grab my public key here:{" "} <LockIcon
size="0.975em"
style={{
marginRight: "0.15em",
stroke: "var(--colors-warning)",
verticalAlign: "middle",
}}
/>{" "}
You can grab my public key here:{" "}
<Link href="https://jrvs.io/pgp" title="My Public Key"> <Link href="https://jrvs.io/pgp" title="My Public Key">
<code <code
style={{ style={{

View File

@ -17,6 +17,14 @@ export const metadata = addMetadata({
alternates: { alternates: {
canonical: "/hillary", canonical: "/hillary",
}, },
openGraph: {
videos: [
{
url: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
type: "video/webm",
},
],
},
}); });
const Page = () => { const Page = () => {

View File

@ -13,7 +13,6 @@
} }
.container { .container {
display: block;
width: 100%; width: 100%;
max-width: var(--max-width); max-width: var(--max-width);
margin: 0 auto; margin: 0 auto;

View File

@ -17,6 +17,14 @@ export const metadata = addMetadata({
alternates: { alternates: {
canonical: "/leo", canonical: "/leo",
}, },
openGraph: {
videos: [
{
url: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
type: "video/webm",
},
],
},
}); });
const Page = () => { const Page = () => {

View File

@ -1,5 +1,6 @@
.meta { .meta {
display: inline-flex; display: flex;
justify-items: flex-start;
flex-wrap: wrap; flex-wrap: wrap;
font-size: 0.925em; font-size: 0.925em;
line-height: 2.3; line-height: 2.3;
@ -8,6 +9,8 @@
} }
.meta .metaItem { .meta .metaItem {
display: inline-flex;
align-items: center;
margin-right: 1.6em; margin-right: 1.6em;
white-space: nowrap; white-space: nowrap;
} }
@ -17,17 +20,16 @@
} }
.meta .metaIcon { .meta .metaIcon {
display: inline-block; width: 1.25em;
width: 1.2em; height: 1.25em;
height: 1.2em;
vertical-align: -0.225em;
margin-right: 0.6em; margin-right: 0.6em;
flex-shrink: 0;
} }
.meta .metaTags { .meta .metaTags {
white-space: normal;
display: inline-flex; display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
white-space: normal;
} }
.meta .metaTag { .meta .metaTag {

View File

@ -1,7 +1,8 @@
import { env } from "../../../lib/env"; import { env } from "../../../lib/env";
import { Suspense } from "react"; import { Suspense } from "react";
import { JsonLd } from "react-schemaorg"; import { JsonLd } from "react-schemaorg";
import { CalendarIcon, TagIcon, SquarePenIcon, EyeIcon } from "lucide-react"; import clsx from "clsx";
import { CalendarDaysIcon, TagIcon, SquarePenIcon, EyeIcon } from "lucide-react";
import Link from "../../../components/Link"; import Link from "../../../components/Link";
import Time from "../../../components/Time"; import Time from "../../../components/Time";
import Comments from "../../../components/Comments"; import Comments from "../../../components/Comments";
@ -89,16 +90,14 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
/> />
<div className={styles.meta}> <div className={styles.meta}>
<div className={styles.metaItem}> <Link href={`/${POSTS_DIR}/${frontmatter!.slug}`} plain className={clsx(styles.metaItem, styles.metaLink)}>
<Link href={`/${POSTS_DIR}/${frontmatter!.slug}`} plain className={styles.metaLink}> <CalendarDaysIcon size="1.25em" className={styles.metaIcon} />
<CalendarIcon size="1.2em" className={styles.metaIcon} />
<Time date={frontmatter!.date} format="MMMM d, y" /> <Time date={frontmatter!.date} format="MMMM d, y" />
</Link> </Link>
</div>
{frontmatter!.tags && ( {frontmatter!.tags && (
<div className={styles.metaItem}> <div className={styles.metaItem}>
<TagIcon size="1.2em" className={styles.metaIcon} /> <TagIcon size="1.25em" className={styles.metaIcon} />
<span className={styles.metaTags}> <span className={styles.metaTags}>
{frontmatter!.tags.map((tag) => ( {frontmatter!.tags.map((tag) => (
<span key={tag} title={tag} className={styles.metaTag} aria-label={`Tagged with ${tag}`}> <span key={tag} title={tag} className={styles.metaTag} aria-label={`Tagged with ${tag}`}>
@ -109,17 +108,15 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
</div> </div>
)} )}
<div className={styles.metaItem}>
<Link <Link
href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter!.slug}/index.mdx`} href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter!.slug}/index.mdx`}
title={`Edit "${frontmatter!.title}" on GitHub`} title={`Edit "${frontmatter!.title}" on GitHub`}
plain plain
className={styles.metaLink} className={clsx(styles.metaItem, styles.metaLink)}
> >
<SquarePenIcon size="1.2em" className={styles.metaIcon} /> <SquarePenIcon size="1.25em" className={styles.metaIcon} />
<span>Improve This Post</span> <span>Improve This Post</span>
</Link> </Link>
</div>
<div <div
className={styles.metaItem} className={styles.metaItem}
@ -129,7 +126,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
marginRight: 0, marginRight: 0,
}} }}
> >
<EyeIcon size="1.2em" className={styles.metaIcon} /> <EyeIcon size="1.25em" className={styles.metaIcon} />
<Suspense <Suspense
// when this loads, the component will count up from zero to the actual number of hits, so we can simply // 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" // show a zero here as a "loading indicator"

View File

@ -28,7 +28,6 @@
} }
.wave { .wave {
display: inline-block;
margin-left: 0.1em; margin-left: 0.1em;
font-size: 1.2em; font-size: 1.2em;
} }

View File

@ -275,7 +275,7 @@ const Page = () => {
darkColor="#959595" darkColor="#959595"
plain plain
> >
<LockIcon size="1.25em" style={{ verticalAlign: "-0.25em" }} />{" "} <LockIcon size="1.25em" style={{ verticalAlign: "text-top" }} />{" "}
<code <code
style={{ style={{
margin: "0 0.15em", margin: "0 0.15em",

View File

@ -58,13 +58,12 @@ export const WindowsLogo = () => (
stroke="currentColor" stroke="currentColor"
strokeWidth="0" strokeWidth="0"
viewBox="0 0 24 24" viewBox="0 0 24 24"
height="1.2em" width="1.25em"
width="1.2em" height="1.25em"
style={{ style={{
display: "inline", width: "1.25em",
width: "1.2em", height: "1.25em",
height: "1.2em", verticalAlign: "text-bottom",
verticalAlign: "-0.15em",
marginRight: "0.1em", marginRight: "0.1em",
}} }}
> >

View File

@ -174,20 +174,19 @@ const Page = async () => {
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
height="1.2em"
width="1.2em" width="1.2em"
height="1.2em"
style={{ style={{
display: "inline",
width: "1.2em", width: "1.2em",
height: "1.2em", height: "1.2em",
verticalAlign: "-0.2em", verticalAlign: "text-top",
margin: "0 0.15em", margin: "0 0.1em 0 0.25em",
fill: "var(--colors-text)", fill: "var(--colors-text)",
}} }}
> >
<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 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>{" "} </svg>{" "}
GitHub... GitHub.
</Link> </Link>
</p> </p>
</> </>

View File

@ -37,15 +37,13 @@
} }
.icon { .icon {
display: inline;
width: 1.25em; width: 1.25em;
height: 1.25em; height: 1.25em;
vertical-align: -0.25em;
margin: 0 0.1em; margin: 0 0.1em;
vertical-align: text-top;
} }
.heart { .heart {
display: inline-block;
color: var(--colors-error); color: var(--colors-error);
} }

View File

@ -26,11 +26,7 @@ const Footer = ({ className, ...rest }: FooterProps) => {
</div> </div>
<div> <div>
Made with{" "} Made with <HeartIcon size="1.25em" fill="currentColor" className={clsx(styles.icon, styles.heart)} /> and{" "}
<span className={styles.heart} title="Love">
<HeartIcon size="1.25em" fill="currentColor" className={styles.icon} />
</span>{" "}
and{" "}
<Link <Link
href="https://nextjs.org/" href="https://nextjs.org/"
title="Powered by Next.js" title="Powered by Next.js"
@ -44,8 +40,8 @@ const Footer = ({ className, ...rest }: FooterProps) => {
stroke="currentColor" stroke="currentColor"
strokeWidth="0" strokeWidth="0"
viewBox="0 0 24 24" viewBox="0 0 24 24"
height="1.25em"
width="1.25em" width="1.25em"
height="1.25em"
className={styles.icon} className={styles.icon}
> >
<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" /> <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" />

View File

@ -22,7 +22,7 @@
} }
.home { .home {
display: inline-flex; display: flex;
flex-shrink: 0; flex-shrink: 0;
align-items: center; align-items: center;
color: var(--colors-medium-dark) !important; color: var(--colors-medium-dark) !important;

View File

@ -1,11 +1,13 @@
.menu { .menu {
display: inline-flex; display: flex;
flex-direction: row;
align-items: center;
padding: 0; padding: 0;
margin: 0; margin: 0;
} }
.item { .item {
display: inline-block; display: block;
margin-left: 1em; margin-left: 1em;
list-style: none; list-style: none;
} }

View File

@ -27,7 +27,13 @@ const Menu = ({ className, ...rest }: MenuProps) => {
); );
})} })}
<li className={styles.item}> <li
className={styles.item}
style={{
// manually align the theme toggle with the rest of the menu icons
paddingTop: "0.2em",
}}
>
<MenuItem <MenuItem
// @ts-expect-error // @ts-expect-error
icon={ThemeToggle} icon={ThemeToggle}

View File

@ -1,6 +1,8 @@
.link { .link {
display: inline-block; display: inline-flex;
align-items: center;
padding: 0.6em; padding: 0.6em;
margin-top: 0.2em;
color: var(--colors-medium-dark) !important; color: var(--colors-medium-dark) !important;
} }
@ -17,17 +19,16 @@
} }
.icon { .icon {
display: inline-block; display: block;
width: 1.25em; width: 1.25em;
height: 1.25em; height: 1.25em;
vertical-align: -0.3em;
} }
.label { .label {
margin-left: 0.7em;
font-size: 0.925em; font-size: 0.925em;
font-weight: 500; font-weight: 500;
letter-spacing: 0.025em; letter-spacing: 0.025em;
margin-left: 0.7em;
} }
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@ -1,4 +1,5 @@
.toggle { .toggle {
display: block;
border: 0; border: 0;
padding: 0.6em; padding: 0.6em;
margin-right: -0.6em; margin-right: -0.6em;
@ -15,7 +16,7 @@
/* hacky way to avoid flashing icon for a few milliseconds on initial render */ /* hacky way to avoid flashing icon for a few milliseconds on initial render */
.toggle > .sun, .toggle > .sun,
[data-theme="dark"] .toggle > .moon { [data-theme="dark"] .toggle > .moon {
display: inline-block; display: inherit;
} }
/* stylelint-disable-next-line no-descending-specificity */ /* stylelint-disable-next-line no-descending-specificity */

View File

@ -1,4 +1,5 @@
import { env } from "../../lib/env"; import { env } from "../../lib/env";
import { cache } from "react";
import path from "path"; import path from "path";
import fs from "fs/promises"; import fs from "fs/promises";
import glob from "fast-glob"; import glob from "fast-glob";
@ -20,7 +21,7 @@ export type FrontMatter = {
}; };
/** Use filesystem to get a simple list of all post slugs */ /** Use filesystem to get a simple list of all post slugs */
export const getSlugs = async (): Promise<string[]> => { export const getSlugs = cache(async (): Promise<string[]> => {
// list all .mdx files in POSTS_DIR // list all .mdx files in POSTS_DIR
const mdxFiles = await glob("*/index.mdx", { const mdxFiles = await glob("*/index.mdx", {
cwd: path.join(process.cwd(), POSTS_DIR), cwd: path.join(process.cwd(), POSTS_DIR),
@ -31,7 +32,7 @@ export const getSlugs = async (): Promise<string[]> => {
const slugs = mdxFiles.map((fileName) => fileName.replace(/\/index\.mdx$/, "")); const slugs = mdxFiles.map((fileName) => fileName.replace(/\/index\.mdx$/, ""));
return slugs; return slugs;
}; });
// overloaded to return either the front matter of a single post or ALL posts // overloaded to return either the front matter of a single post or ALL posts
export const getFrontMatter: { export const getFrontMatter: {
@ -43,11 +44,12 @@ export const getFrontMatter: {
* Parses and returns the front matter of a given slug, or undefined if the slug does not exist * Parses and returns the front matter of a given slug, or undefined if the slug does not exist
*/ */
(slug: string): Promise<FrontMatter | undefined>; (slug: string): Promise<FrontMatter | undefined>;
} = async ( } = cache(
async (
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
slug?: any slug?: any
): // eslint-disable-next-line @typescript-eslint/no-explicit-any ): // eslint-disable-next-line @typescript-eslint/no-explicit-any
Promise<any> => { Promise<any> => {
if (typeof slug === "string") { if (typeof slug === "string") {
try { try {
const { frontmatter } = await import(`../../${POSTS_DIR}/${slug}/index.mdx`); const { frontmatter } = await import(`../../${POSTS_DIR}/${slug}/index.mdx`);
@ -98,10 +100,11 @@ Promise<any> => {
} }
throw new Error("getFrontMatter() called with invalid argument."); throw new Error("getFrontMatter() called with invalid argument.");
}; }
);
/** Returns the content of a post with very limited processing to include in RSS feeds */ /** Returns the content of a post with very limited processing to include in RSS feeds */
export const getContent = async (slug: string): Promise<string | undefined> => { export const getContent = cache(async (slug: string): Promise<string | undefined> => {
try { try {
// TODO: also remove MDX-related syntax (e.g. import/export statements) // TODO: also remove MDX-related syntax (e.g. import/export statements)
const content = await unified() const content = await unified()
@ -139,4 +142,4 @@ export const getContent = async (slug: string): Promise<string | undefined> => {
console.error(`Failed to load/parse content for post with slug "${slug}":`, error); console.error(`Failed to load/parse content for post with slug "${slug}":`, error);
return undefined; return undefined;
} }
}; });

View File

@ -11,8 +11,13 @@ import "./lib/env";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
reactStrictMode: true, reactStrictMode: true,
productionBrowserSourceMaps: true,
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"], pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
outputFileTracingIncludes: { outputFileTracingIncludes: {
"/notes/[slug]/opengraph-image": [ "/notes/[slug]/opengraph-image": [
"./notes/**/*", "./notes/**/*",
@ -24,6 +29,7 @@ const nextConfig: NextConfig = {
outputFileTracingExcludes: { outputFileTracingExcludes: {
"*": ["./public/**/*", "**/*.mp4", "**/*.webm", "**/*.vtt"], "*": ["./public/**/*", "**/*.mp4", "**/*.webm", "**/*.vtt"],
}, },
productionBrowserSourceMaps: true,
webpack: (config) => { webpack: (config) => {
config.module.rules.push({ config.module.rules.push({
test: /\.(mp4|webm|vtt)$/i, test: /\.(mp4|webm|vtt)$/i,
@ -50,9 +56,6 @@ const nextConfig: NextConfig = {
], ],
}, },
}, },
eslint: {
dirs: ["app", "components", "contexts", "hooks", "lib", "notes"],
},
headers: async () => [ headers: async () => [
{ {
// matches any path // matches any path

View File

@ -22,13 +22,12 @@ export const OctocatLink = ({ repo }) => {
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
strokeWidth="0" strokeWidth="0"
viewBox="0 0 24 24" viewBox="0 0 24 24"
height="1.2em"
width="1.2em" width="1.2em"
height="1.2em"
style={{ style={{
display: "inline",
height: "1.2em",
width: "1.2em", width: "1.2em",
verticalAlign: "-0.2em", height: "1.2em",
verticalAlign: "text-top",
fill: "var(--colors-text)", fill: "var(--colors-text)",
}} }}
> >

View File

@ -13,8 +13,9 @@
"dev": "next dev -H 0.0.0.0", "dev": "next dev -H 0.0.0.0",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "eslint .",
"prepare": "husky" "typecheck": "tsc --noEmit",
"prepare": "test -d node_modules/husky && husky || echo \"skipping husky\""
}, },
"dependencies": { "dependencies": {
"@date-fns/tz": "^1.2.0", "@date-fns/tz": "^1.2.0",
@ -37,7 +38,7 @@
"feed": "^4.2.2", "feed": "^4.2.2",
"geist": "^1.3.1", "geist": "^1.3.1",
"html-entities": "^2.6.0", "html-entities": "^2.6.0",
"lucide-react": "0.488.0", "lucide-react": "0.501.0",
"next": "15.3.1-canary.13", "next": "15.3.1-canary.13",
"polished": "^4.3.1", "polished": "^4.3.1",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",

80
pnpm-lock.yaml generated
View File

@ -40,7 +40,7 @@ importers:
version: 15.26.0 version: 15.26.0
'@t3-oss/env-nextjs': '@t3-oss/env-nextjs':
specifier: ^0.12.0 specifier: ^0.12.0
version: 0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.2) version: 0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.3)
'@upstash/redis': '@upstash/redis':
specifier: ^1.34.8 specifier: ^1.34.8
version: 1.34.8 version: 1.34.8
@ -69,8 +69,8 @@ importers:
specifier: ^2.6.0 specifier: ^2.6.0
version: 2.6.0 version: 2.6.0
lucide-react: lucide-react:
specifier: 0.488.0 specifier: 0.501.0
version: 0.488.0(react@19.1.0) version: 0.501.0(react@19.1.0)
next: next:
specifier: 15.3.1-canary.13 specifier: 15.3.1-canary.13
version: 15.3.1-canary.13(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-ebf51a3-20250411)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 15.3.1-canary.13(@babel/core@7.26.10)(babel-plugin-react-compiler@19.0.0-beta-ebf51a3-20250411)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -417,8 +417,8 @@ packages:
'@emotion/hash@0.9.2': '@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@eslint-community/eslint-utils@4.6.0': '@eslint-community/eslint-utils@4.6.1':
resolution: {integrity: sha512-WhCn7Z7TauhBtmzhvKpoQs0Wwb/kBcy4CwpuI0/eEIr2Lx2auxmulAzLr91wVZJaz47iUZdkXOK7WlAfxGKCnA==} resolution: {integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@ -648,8 +648,8 @@ packages:
'@types/react': '>=16' '@types/react': '>=16'
react: '>=16' react: '>=16'
'@napi-rs/wasm-runtime@0.2.8': '@napi-rs/wasm-runtime@0.2.9':
resolution: {integrity: sha512-OBlgKdX7gin7OIq4fadsjpg+cp2ZphvAIKucHsNfTdJiqdOmOEwQd/bHi0VwNrcw5xpBJyUw6cK/QilCqy1BSg==} resolution: {integrity: sha512-OKRBiajrrxB9ATokgEQoG87Z25c67pCpYcCwmXYX8PBftC9pBfN18gnm/fh1wurSLEKIAt+QRFLFCQISrb66Jg==}
'@next/bundle-analyzer@15.3.1-canary.13': '@next/bundle-analyzer@15.3.1-canary.13':
resolution: {integrity: sha512-dyobhAPSrAistAAW9eilkwrqv6Ie4RTDtZhvgD0MZQTpW6ftuV93c3LqEHMrxVgmCG6eMPe3dRd1DJYZF0faog==} resolution: {integrity: sha512-dyobhAPSrAistAAW9eilkwrqv6Ie4RTDtZhvgD0MZQTpW6ftuV93c3LqEHMrxVgmCG6eMPe3dRd1DJYZF0faog==}
@ -1272,8 +1272,8 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
caniuse-lite@1.0.30001713: caniuse-lite@1.0.30001714:
resolution: {integrity: sha512-wCIWIg+A4Xr7NfhTuHdX+/FKh3+Op3LBbSp2N5Pfx6T/LhdQy3GTyoTg48BReaW/MyMNZAkTadsBtai3ldWK0Q==} resolution: {integrity: sha512-mtgapdwDLSSBnCI3JokHM7oEQBLxiJKVRtg10AxM1AyeiKcM96f0Mkbqeq+1AbiCtvMcHRulAAEMu693JrSWqg==}
ccount@2.0.1: ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@ -1506,8 +1506,8 @@ packages:
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
electron-to-chromium@1.5.137: electron-to-chromium@1.5.138:
resolution: {integrity: sha512-/QSJaU2JyIuTbbABAo/crOs+SuAZLS+fVVS10PVrIT9hrRkmZl8Hb0xPSkKRUUWHQtYzXHpQUW3Dy5hwMzGZkA==} resolution: {integrity: sha512-FWlQc52z1dXqm+9cCJ2uyFgJkESd+16j6dBEjsgDNuHjBpuIzL8/lRc0uvh1k8RNI6waGo6tcy2DvwkTBJOLDg==}
emoji-regex-xs@1.0.0: emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
@ -2440,8 +2440,8 @@ packages:
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
lucide-react@0.488.0: lucide-react@0.501.0:
resolution: {integrity: sha512-ronlL0MyKut4CEzBY/ai2ZpKPxyWO4jUqdAkm2GNK5Zn3Rj+swDz+3lvyAUXN0PNqPKIX6XM9Xadwz/skLs/pQ==} resolution: {integrity: sha512-E2KoyhW59fCb/yUbR3rbDer83fqn7a8NG91ZhIot2yWaPHjPyGzzsNKh40N//GezYShAuycf3TcQksRQznIsRw==}
peerDependencies: peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
@ -2780,8 +2780,8 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
oniguruma-parser@0.11.1: oniguruma-parser@0.11.2:
resolution: {integrity: sha512-fX6SirDOsTUNqSUOnL3fDtD3R7PCXNWGA3WWPvv9egEfTWkNXzRLO/9CC1WkDusP6HyWRZig06kHeYPcw3mlqQ==} resolution: {integrity: sha512-F7Ld4oDZJCI5/wCZ8AOffQbqjSzIRpKH7I/iuSs1SkhZeCj0wS6PMZ4W6VA16TWHrAo0Y9bBKEJOe7tvwcTXnw==}
oniguruma-to-es@4.2.0: oniguruma-to-es@4.2.0:
resolution: {integrity: sha512-MDPs6KSOLS0tKQ7joqg44dRIRZUyotfTy0r+7oEEs6VwWWP0+E2PPDYWMFN0aqOjRyWHBYq7RfKw9GQk2S2z5g==} resolution: {integrity: sha512-MDPs6KSOLS0tKQ7joqg44dRIRZUyotfTy0r+7oEEs6VwWWP0+E2PPDYWMFN0aqOjRyWHBYq7RfKw9GQk2S2z5g==}
@ -3760,8 +3760,8 @@ packages:
peerDependencies: peerDependencies:
zod: ^3.18.0 zod: ^3.18.0
zod@3.24.2: zod@3.24.3:
resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
zwitch@2.0.4: zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
@ -3970,7 +3970,7 @@ snapshots:
'@emotion/hash@0.9.2': {} '@emotion/hash@0.9.2': {}
'@eslint-community/eslint-utils@4.6.0(eslint@9.24.0)': '@eslint-community/eslint-utils@4.6.1(eslint@9.24.0)':
dependencies: dependencies:
eslint: 9.24.0 eslint: 9.24.0
eslint-visitor-keys: 3.4.3 eslint-visitor-keys: 3.4.3
@ -4199,7 +4199,7 @@ snapshots:
'@types/react': 19.1.2 '@types/react': 19.1.2
react: 19.1.0 react: 19.1.0
'@napi-rs/wasm-runtime@0.2.8': '@napi-rs/wasm-runtime@0.2.9':
dependencies: dependencies:
'@emnapi/core': 1.4.3 '@emnapi/core': 1.4.3
'@emnapi/runtime': 1.4.3 '@emnapi/runtime': 1.4.3
@ -4417,19 +4417,19 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@t3-oss/env-core@0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.2)': '@t3-oss/env-core@0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.3)':
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
valibot: 1.0.0(typescript@5.8.3) valibot: 1.0.0(typescript@5.8.3)
zod: 3.24.2 zod: 3.24.3
'@t3-oss/env-nextjs@0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.2)': '@t3-oss/env-nextjs@0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.3)':
dependencies: dependencies:
'@t3-oss/env-core': 0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.2) '@t3-oss/env-core': 0.12.0(typescript@5.8.3)(valibot@1.0.0(typescript@5.8.3))(zod@3.24.3)
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
valibot: 1.0.0(typescript@5.8.3) valibot: 1.0.0(typescript@5.8.3)
zod: 3.24.2 zod: 3.24.3
'@tybys/wasm-util@0.9.0': '@tybys/wasm-util@0.9.0':
dependencies: dependencies:
@ -4561,7 +4561,7 @@ snapshots:
'@typescript-eslint/utils@8.30.1(eslint@9.24.0)(typescript@5.8.3)': '@typescript-eslint/utils@8.30.1(eslint@9.24.0)(typescript@5.8.3)':
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.6.0(eslint@9.24.0) '@eslint-community/eslint-utils': 4.6.1(eslint@9.24.0)
'@typescript-eslint/scope-manager': 8.30.1 '@typescript-eslint/scope-manager': 8.30.1
'@typescript-eslint/types': 8.30.1 '@typescript-eslint/types': 8.30.1
'@typescript-eslint/typescript-estree': 8.30.1(typescript@5.8.3) '@typescript-eslint/typescript-estree': 8.30.1(typescript@5.8.3)
@ -4615,7 +4615,7 @@ snapshots:
'@unrs/resolver-binding-wasm32-wasi@1.5.0': '@unrs/resolver-binding-wasm32-wasi@1.5.0':
dependencies: dependencies:
'@napi-rs/wasm-runtime': 0.2.8 '@napi-rs/wasm-runtime': 0.2.9
optional: true optional: true
'@unrs/resolver-binding-win32-arm64-msvc@1.5.0': '@unrs/resolver-binding-win32-arm64-msvc@1.5.0':
@ -4792,8 +4792,8 @@ snapshots:
browserslist@4.24.4: browserslist@4.24.4:
dependencies: dependencies:
caniuse-lite: 1.0.30001713 caniuse-lite: 1.0.30001714
electron-to-chromium: 1.5.137 electron-to-chromium: 1.5.138
node-releases: 2.0.19 node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4) update-browserslist-db: 1.1.3(browserslist@4.24.4)
@ -4832,7 +4832,7 @@ snapshots:
callsites@3.1.0: {} callsites@3.1.0: {}
caniuse-lite@1.0.30001713: {} caniuse-lite@1.0.30001714: {}
ccount@2.0.1: {} ccount@2.0.1: {}
@ -5044,7 +5044,7 @@ snapshots:
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
electron-to-chromium@1.5.137: {} electron-to-chromium@1.5.138: {}
emoji-regex-xs@1.0.0: {} emoji-regex-xs@1.0.0: {}
@ -5352,8 +5352,8 @@ snapshots:
'@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.10) '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.26.10)
eslint: 9.24.0 eslint: 9.24.0
hermes-parser: 0.25.1 hermes-parser: 0.25.1
zod: 3.24.2 zod: 3.24.3
zod-validation-error: 3.4.0(zod@3.24.2) zod-validation-error: 3.4.0(zod@3.24.3)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -5394,7 +5394,7 @@ snapshots:
eslint@9.24.0: eslint@9.24.0:
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.6.0(eslint@9.24.0) '@eslint-community/eslint-utils': 4.6.1(eslint@9.24.0)
'@eslint-community/regexpp': 4.12.1 '@eslint-community/regexpp': 4.12.1
'@eslint/config-array': 0.20.0 '@eslint/config-array': 0.20.0
'@eslint/config-helpers': 0.2.1 '@eslint/config-helpers': 0.2.1
@ -6239,7 +6239,7 @@ snapshots:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
lucide-react@0.488.0(react@19.1.0): lucide-react@0.501.0(react@19.1.0):
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
@ -6744,7 +6744,7 @@ snapshots:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
'@swc/helpers': 0.5.15 '@swc/helpers': 0.5.15
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001713 caniuse-lite: 1.0.30001714
postcss: 8.4.31 postcss: 8.4.31
react: 19.1.0 react: 19.1.0
react-dom: 19.1.0(react@19.1.0) react-dom: 19.1.0(react@19.1.0)
@ -6856,12 +6856,12 @@ snapshots:
dependencies: dependencies:
mimic-function: 5.0.1 mimic-function: 5.0.1
oniguruma-parser@0.11.1: {} oniguruma-parser@0.11.2: {}
oniguruma-to-es@4.2.0: oniguruma-to-es@4.2.0:
dependencies: dependencies:
emoji-regex-xs: 1.0.0 emoji-regex-xs: 1.0.0
oniguruma-parser: 0.11.1 oniguruma-parser: 0.11.2
regex: 6.0.1 regex: 6.0.1
regex-recursion: 6.0.2 regex-recursion: 6.0.2
@ -8165,10 +8165,10 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod-validation-error@3.4.0(zod@3.24.2): zod-validation-error@3.4.0(zod@3.24.3):
dependencies: dependencies:
zod: 3.24.2 zod: 3.24.3
zod@3.24.2: {} zod@3.24.3: {}
zwitch@2.0.4: {} zwitch@2.0.4: {}