Phase 1 of the functional design plan. - Move the old site into legacy/, unchanged, for reference. It still builds from there until cutover. - Add packages/schema: the one definition of all 20 note types, with field titles and descriptions, note and vault checks, and JSON Schema export so later editing tools and the MCP server share it. - Add vault-kit: Obsidian templates generated from the schema, and the Site guide for 24.01_PersonalWebsite. - Add tools/migrate: turns the legacy JSON, collections, store data and page text into 545 Site folder notes and 84 images, and checks the result with the schema. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012aSbo2D1qP8cMFQCykf7kx
6.1 KiB
Greg Kilmer — Profile & Portfolio Site
A responsive, content-driven personal site for Greg Kilmer, built as two wings of the same house: a polished professional resume/portfolio on one side, and a warm, personality-driven interests hub on the other.
Built with Astro 7 + React Islands + TypeScript, served by a Node server with API routes, ready to deploy as a container on a homelab Cosmos system.
Highlights
- Astro 7 static-first core with React Islands for interactivity (mobile menu, theme toggle, animated hero background, contact form).
- Node backend (
@astrojs/node, standalone) with a typed/api/contactendpoint (SMTP via nodemailer + webhook fallback, Zod validation, rate limit). - Section-aware theming: the Professional wing reads dark/polished, the Interests wing reads warm/inviting, plus a global light/dark toggle.
- Rich motion: animated mesh-gradient hero with an optional canvas particle
field, IntersectionObserver scroll reveals, View Transitions page swaps,
scroll-triggered nav blur. All
prefers-reduced-motionaware. - Content Collections (typed): add projects and experience by dropping markdown/MDX files.
- mdi icons everywhere (no emojis, per the spec), inlined at build time from
@iconify-json/mdifor zero runtime cost. - Self-hosted variable fonts (Inter, Plus Jakarta Sans, JetBrains Mono).
- Accessible: semantic landmarks, skip link, keyboard-operable menu, focus-visible rings, ARIA on interactive bits, WCAG-AA contrast targets.
- Deploy artifacts: multi-stage
Dockerfile+docker-compose.yml.
Quickstart
# 1. Install dependencies (Node 20+)
npm install
# 2. Start the dev server with hot reload
npm run dev
# -> http://localhost:4321
# 3. Production build (runs astro check + build)
npm run build
# 4. Run the built Node server
npm run serve
# -> http://localhost:4321
Environment is optional for local dev. To enable contact-form delivery, copy
.env.example to .env and fill in SMTP or webhook vars (see
docs/DEPLOYMENT.md).
Project structure
src/
├── components/
│ ├── hero/ AnimatedBackground.tsx (gradient + canvas particles)
│ ├── motion/ Reveal.astro (IntersectionObserver scroll reveals)
│ ├── nav/ NavBar.astro, MobileMenu.tsx, ThemeToggle.tsx
│ ├── cards/ InterestCard.astro
│ ├── professional/ Timeline, SkillGrid, ProjectCard, SectionNav, ContactForm
│ ├── layout/ Footer, SectionHeader, SplitTransition, InterestHero,
│ │ RelatedInterests, Placeholder
│ └── Icon.astro Inline mdi icon renderer (build-time)
├── content/
│ ├── projects/ Project collection entries (markdown/MDX)
│ └── experience/ Experience collection entries
├── layouts/
│ └── BaseLayout.astro <html> shell, head, fonts, theme init, nav, footer
├── pages/
│ ├── index.astro Home (hero + snapshot grid)
│ ├── about.astro About + split transition fork
│ ├── professional/index.astro Single long page: Summary, Experience,
│ │ Skills, Projects, Education, Contact
│ ├── interests/ Hub + 5 sub-pages
│ │ ├── index.astro
│ │ ├── ttrpgs / gaming / reading / making / pokemon .astro
│ ├── contact.astro Two-column connect page + contact form
│ └── api/contact.ts POST endpoint -> SMTP / webhook
├── content.config.ts Collection schemas (projects, experience)
├── lib/ site.ts (nav/socials), motion.ts, content helpers
├── styles/ global.css, themes.css, animations.css
└── scripts/theme-init.ts No-FOUC theme boot (inlined in <head>)
The two wings (theming model)
Theming has two independent axes (see docs/THEMING.md):
- Global light/dark on
<html data-theme>, set before paint by an inline script, persisted tolocalStorage, toggleable via the nav button. - Section tone via
data-surfaceon<body>:neutral(Home/About/Contact) — balanced middle groundprofessional— dark slate, polishedinterest— warm cream/amber, inviting
Components only reference semantic CSS variables (--surface, --text,
--accent...), so changing either axis restyles everything automatically.
Adding content
- Projects / Experience: drop a markdown or MDX file in
src/content/projects/orsrc/content/experience/. Schemas and field reference are in docs/CONTENT.md. - Interest pages: hand-built
.astrofiles undersrc/pages/interests/, each following the spec's template (Header, Overview, Highlights, Currently, Related). Edit copy in place. - Placeholders: every
[Greg to fill]gap from the spec is a styledPlaceholderslot with a TODO badge so you can find and replace them.
Deployment (Cosmos homelab)
The site runs as a Node server inside a container. Full walkthrough in docs/DEPLOYMENT.md:
docker compose up -d --build
# Then point your reverse proxy at port 4321 with TLS for gregkilmer.com.
Scripts
| Script | Description |
|---|---|
npm run dev |
Dev server with hot reload |
npm run build |
Type-check (astro check) then build the Node server |
npm run serve |
Run the built server (dist/server/entry.mjs) |
npm run preview |
Preview the build locally |
npm run check |
Type-check only |
Tech stack
Astro 7, React 19, TypeScript 6, Tailwind CSS v4, Framer Motion, nodemailer,
Zod, @iconify-json/mdi, Fontsource variable fonts, @astrojs/node standalone.
License
Personal site for Greg Kilmer. All rights reserved.