1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-10-30 03:36:03 -04:00
2025-05-06 19:07:23 -04:00
parent bea2a55f77
commit a08ec532b3
9 changed files with 93 additions and 34 deletions

59
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,59 @@
# Project Information for GitHub Copilot
This repository contains the source code for [jarv.is](https://jarv.is/), a personal website for Jake Jarvis built with Next.js.
## Project Overview
- **Type**: Personal website with blog
- **Framework**: Next.js (uses latest features like App Router, Partial Prerendering)
- **Primary Language**: TypeScript
- **Styling**: Tailwind CSS
- **Data Storage**: Upstash Redis
- **Content**: MDX for blog posts with custom components
- **Deployment**: Vercel
## Project Structure
```
/
├── app/ # Pages and layouts using Next.js App Router
├── components/ # Reusable React components
├── lib/ # Utility functions, configurations, and helpers
├── notes/ # Blog posts written in MDX format along with any images
└── public/ # Static assets
```
## Code Style & Conventions
- **Formatting**: Prettier is used for code formatting
- **Linting**: ESLint with custom configuration
- **JavaScript/TypeScript**:
- Use double quotes for strings
- 2-space indentation (no tabs)
- Trailing commas in objects and arrays
- Maximum line length of 120 characters
- TypeScript is strictly typed
- **React Components**:
- Prefer function components with hooks
- Use named exports for components
- Client components are marked with "use client" directive
- **CSS**:
- Use Tailwind utility classes
- Custom CSS variables for theming (light/dark mode)
- `cn()` from `lib/utils.ts` utility for conditional class names
## Preferred Solutions
- When suggesting code changes, maintain the existing patterns and structure
- **React Server Components when possible, Client Components when necessary**
- MDX for content-heavy pages
- Image optimization through Next.js Image component
- Cache and revalidate data appropriately with tags
## Other Notes
- Dynamic theme switching (light/dark mode) is supported
- Accessibility is important - maintain proper heading levels, skip links, etc.
- Performance optimization is a priority (bundle sizes, image optimization, etc.)
This document should be updated when significant architectural or dependency changes are made to the project.

View File

@@ -18,8 +18,14 @@ const Page = () => {
<p className="my-5 text-[0.925rem] leading-relaxed md:text-base">
Fill out this quick form and I&rsquo;ll get back to you as soon as I can! You can also{" "}
<Link href="mailto:jake@jarv.is">email me directly</Link> or send me a direct message on{" "}
<Link href="https://bsky.app/profile/jarv.is">🦋 Bluesky</Link> or{" "}
<Link href="https://fediverse.jarv.is/@jake">🦣 Mastodon</Link>.
<Link href="https://bsky.app/profile/jarv.is" className="text-nowrap">
🦋 Bluesky
</Link>{" "}
or{" "}
<Link href="https://fediverse.jarv.is/@jake" className="text-nowrap">
🦣 Mastodon
</Link>
.
</p>
<p className="my-5 text-[0.925rem] leading-relaxed md:text-base">
You can grab my public key here:{" "}

View File

@@ -17,12 +17,14 @@ export const metadata = defaultMetadata;
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
<html lang={env.NEXT_PUBLIC_SITE_LOCALE} suppressHydrationWarning>
<html
lang={env.NEXT_PUBLIC_SITE_LOCALE}
className={`${GeistSans.variable} ${GeistMono.variable}`}
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",

View File

@@ -50,7 +50,7 @@ const Page = async () => {
dangerouslySetInnerHTML={{ __html: htmlTitle || title }}
/>
{views > 0 && (
<span className="bg-muted text-muted-foreground inline-flex h-5 flex-nowrap items-center space-x-1 rounded-md px-1.5 align-text-top text-xs font-semibold text-nowrap select-none">
<span className="bg-muted text-muted-foreground inline-flex h-5 flex-nowrap items-center space-x-1 rounded-md px-1.5 align-text-top text-xs font-semibold text-nowrap shadow select-none">
<EyeIcon className="inline-block size-4 shrink-0" />
<span className="inline-block leading-5">
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(views)}

View File

@@ -30,9 +30,9 @@ export const PageStyles = () => (
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;
font-family: ${ComicNeue.style.fontFamily}, var(--font-sans);
font-weight: 700;
font-size: 1em;
text-align: center;
}
main iframe + p em,

View File

@@ -1,14 +1,16 @@
import Button from "@/components/ui/button";
const SKIP_NAV_ID = "skip-nav";
export const SkipNavLink = () => {
return (
<a
href={`#${SKIP_NAV_ID}`}
tabIndex={0}
className="text-primary bg-muted focus:border-ring sr-only z-[1000] underline focus:not-sr-only focus:fixed focus:top-2.5 focus:left-2.5 focus:border-2 focus:border-solid focus:p-4"
<Button
asChild
className="sr-only transition-none focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-100 focus:inline-flex focus:px-4 focus:py-2"
variant="default"
>
Skip to content
</a>
<a href={`#${SKIP_NAV_ID}`}>Skip to content</a>
</Button>
);
};

View File

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

View File

@@ -15,16 +15,18 @@ const Video = ({
{...(typeof src === "string" ? { src } : {})}
{...(autoPlay
? {
autoPlay: true,
preload: "auto",
controls: false,
autoPlay: true,
playsInline: true, // safari autoplay workaround
loop: true,
muted: true,
}
: {
autoPlay: false,
preload: "metadata",
controls: true,
playsInline: true,
})}
crossOrigin="anonymous"
className={cn("mx-auto block h-auto max-h-[500px] w-full", className)}

View File

@@ -10,27 +10,14 @@ import {
export const GeistSans = GeistSansLoader({
subsets: ["latin"],
display: "swap",
fallback: [
// https://github.com/system-fonts/modern-font-stacks#system-ui
"system-ui",
"sans-serif",
],
variable: "--font-geist-sans",
preload: true,
});
export const GeistMono = GeistMonoLoader({
subsets: ["latin"],
display: "swap",
fallback: [
// https://github.com/primer/css/blob/4113637b3bb60cad1e2dca82e70d92ad05694399/src/support/variables/typography.scss#L37
"ui-monospace",
"SFMono-Regular",
"'SF Mono'",
"Menlo",
"Consolas",
"'Liberation Mono'",
"monospace",
],
variable: "--font-geist-mono",
preload: true,
});