diff --git a/CLAUDE.md b/CLAUDE.md index 43c994c..590f739 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1,37 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + @AGENTS.md + +## Commands + +```bash +npm run dev # Start development server +npm run build # Production build +npm run start # Start production server +npm run lint # Run ESLint +``` + +No test runner is configured. + +## Stack + +- **Next.js 16** with App Router — read `node_modules/next/dist/docs/` before writing any Next.js code; APIs may differ from training data +- **Tailwind CSS v4** — configured via PostCSS (`postcss.config.mjs`); no `tailwind.config.*` file; theme tokens defined in `app/globals.css` using `@theme inline` +- **TypeScript** — strict mode, path alias `@/*` maps to project root + +## Architecture + +This is a portfolio site built with the Next.js App Router. All routes live under `app/`. Global styles and theme variables are in `app/globals.css`. Fonts (Geist Sans/Mono) are loaded in `app/layout.tsx` and injected as CSS variables. + +Reusable UI components should go in `components/`. There is no `components/` directory yet — create it when adding the first component. + +## Project Rules + +- Functional components only — no class components +- All styling via Tailwind utility classes — no CSS modules or inline styles +- Dark mode by default; use Tailwind dark-mode utilities and the CSS vars (`--background`, `--foreground`) defined in `globals.css` +- Keep components small and single-purpose +- Prefer readability over cleverness +- Do not add features unless explicitly asked diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000..3724216 --- /dev/null +++ b/app/about/page.tsx @@ -0,0 +1,116 @@ +import Badge from '@/components/Badge' + +const skills = [ + { + category: 'Languages', + items: ['TypeScript', 'JavaScript', 'Go', 'Python', 'SQL', 'Bash'], + }, + { + category: 'Frontend', + items: ['React', 'Next.js', 'Tailwind CSS', 'Radix UI', 'Framer Motion'], + }, + { + category: 'Backend', + items: ['Node.js', 'PostgreSQL', 'Redis', 'REST', 'GraphQL', 'tRPC'], + }, + { + category: 'Tooling', + items: ['Docker', 'Git', 'GitHub Actions', 'Vercel', 'Linux'], + }, + { + category: 'Focus Areas', + items: ['Web Performance', 'Developer Experience', 'API Design', 'UI Engineering', 'Accessibility'], + }, +] + +const timeline = [ + { + year: '2024', + title: 'Senior Engineer — Acme Corp', + description: + 'Leading frontend infrastructure and design systems. Building core platform features used by millions of users.', + }, + { + year: '2022', + title: 'Software Engineer — Startup XYZ', + description: + 'Full-stack product work across a SaaS platform. Owned the billing system, onboarding flow, and internal tooling.', + }, + { + year: '2021', + title: 'Open Source — Deploy CLI', + description: + 'Built and published an open-source CLI tool in Go for automating local dev environment setup.', + }, + { + year: '2020', + title: 'Software Engineer — Agency Co.', + description: + 'Client work spanning e-commerce, marketing sites, and web apps. Introduced TypeScript and component-driven development.', + }, + { + year: '2019', + title: 'BSc Computer Science', + description: 'Graduated with a focus on distributed systems and human-computer interaction.', + }, +] + +export default function AboutPage() { + return ( +
+ + {/* Intro */} +
+

About

+

+ I'm a software engineer with a focus on the web — building products + that are fast, accessible, and well-crafted. I care about the details: + clean APIs, readable code, and interfaces that feel good to use. Outside + of work I contribute to open source, write about things I'm learning, + and tinker with side projects. +

+
+ + {/* Skills */} +
+

Skills

+
+ {skills.map(({ category, items }) => ( +
+
{category}
+
+
    + {items.map((item) => ( +
  • + +
  • + ))} +
+
+
+ ))} +
+
+ + {/* Timeline */} +
+

Timeline

+
    + {timeline.map(({ year, title, description }) => ( +
  1. +
    + {year} +
    +
    +
    +

    {title}

    +

    {description}

    +
    +
  2. + ))} +
+
+ +
+ ) +} diff --git a/app/contact/page.tsx b/app/contact/page.tsx new file mode 100644 index 0000000..212b8da --- /dev/null +++ b/app/contact/page.tsx @@ -0,0 +1,37 @@ +const links = [ + { + label: 'Email', + value: 'jonathan@example.com', + href: 'mailto:jonathan@example.com', + }, + { + label: 'GitHub', + value: 'github.com/jonathan', + href: 'https://github.com/jonathan', + }, +] + +export default function ContactPage() { + return ( +
+

Contact

+

+ The best way to reach me is by email. I'm also on GitHub. +

+ +
+ ) +} diff --git a/app/globals.css b/app/globals.css index a2dc41e..1914c40 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,11 @@ @import "tailwindcss"; - -:root { - --background: #ffffff; - --foreground: #171717; -} +@custom-variant dark (&:is(.dark *)); @theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); } -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - body { - background: var(--background); - color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; + font-family: var(--font-sans); } diff --git a/app/layout.tsx b/app/layout.tsx index 976eb90..ca8361a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,8 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import Navbar from "@/components/Navbar"; +import Footer from "@/components/Footer"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -13,10 +15,23 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "jonathan.dev", + description: "Personal portfolio", }; +// Runs before first paint — restores saved theme, defaults to dark +const themeScript = ` + (function() { + try { + if (localStorage.getItem('theme') === 'light') { + document.documentElement.classList.remove('dark'); + } else { + document.documentElement.classList.add('dark'); + } + } catch {} +})(); +`; + export default function RootLayout({ children, }: Readonly<{ @@ -25,9 +40,17 @@ export default function RootLayout({ return ( - {children} + +