mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-10-30 03:36:03 -04:00
add .github/copilot-instructions.md file
https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot
This commit is contained in:
59
.github/copilot-instructions.md
vendored
Normal file
59
.github/copilot-instructions.md
vendored
Normal 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.
|
||||
@@ -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’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:{" "}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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)}
|
||||
|
||||
17
lib/fonts.ts
17
lib/fonts.ts
@@ -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,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user