diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..272f94c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,55 @@ +# personal-website — Agent Guide + +Dom Mangonon's single-page portfolio, deployed to https://dommango.github.io. Accessibility +guidelines: `ACCESSIBILITY.md`. + +## Stack + +Next.js 16 App Router · React 19 · TS strict · Tailwind 4 · static export (no server runtime, +all data loaded at build time) · GitHub Pages deploy via `.github/workflows/deploy.yml`. + +## Commands + +- `npm install --legacy-peer-deps` — react-simple-maps declares a React <19 peer; CI uses it too +- `npm run dev` / `build` (→ `out/`) / `lint` · `npx tsc --noEmit` +- `npm test -- --run` (vitest; drop `-- --run` for watch mode) · `npx playwright test` (e2e) + +## Hard rules + +- **Never commit Citi/employer-confidential content.** History was purged once already + (2026-08-28: filter-rewrite + force-push across `main`/feature branches) after regulator- + confidential material (Consent Order text, MRA reporting detail) leaked into commits on this + public repo. Career content is gitignored by design — source of record is `~/personal/career`, + not this repo; don't re-add it or reintroduce a sync step. `'Citi · SVP, Transformation'` + (LinkedIn-grade) is fine; verbatim regulatory/internal detail is not. A dangling old SHA + (`cf65730`) may still be GitHub-support-GC pending — don't assume force-push alone is enough. +- Four places must stay in sync when adding/changing a landing section, or the scroll-spy breaks + silently: the component in `components/landing/`, the `SectionId` union in `Nav.tsx`, + `SECTION_IDS` in `BrutalistLanding.tsx` (must match DOM order — spy takes the last element with + `offsetTop <= scrollY`), and the `link()` calls in `Nav.tsx`. A conditional section (e.g. + Writing) needs its nav link gated by the same predicate as the section. +- Build sections from semantic tokens only (`var(--accent)`, `var(--fg)`, `var(--fg-muted)`, + `var(--rule)`, `var(--s-N)`, `var(--font-*)`) in `app/globals.css` — that's what makes all three + themes (Gold/Oxblood/High Contrast) work via `[data-accent]`/`[data-contrast]`. The landing + (`.brutalist-root`) is dark-only by design and shadows the legacy `:root` light block. One + responsive breakpoint, `@media (max-width: 900px)` at the bottom of globals.css — new grids + aren't automatic. Structure convention: `.section` > `` > `.*-head`, order matters + (adjacent-sibling rule supplies top margin). `BinaryRule`'s `seed` prop drives a deterministic + PRNG for hydration safety, not aesthetics — give new sections an unused seed. +- Content is hand-authored TS in `lib/content/`, not markdown/JSON (`resolveJsonModule` + + `strict` infers `never[]` from an empty JSON array, which fails typecheck). Travel data is the + exception — script-generated JSON via `scripts/process-travel-data.js` / + `scripts/fetch-flights.js` (both manual, not automated). +- `github-actions[bot]` pushes with the default `GITHUB_TOKEN` don't trigger workflows, so a cron + that commits data can't make the site rebuild. Anything needing fresh data at deploy time must + fetch during the build, not commit-then-rebuild. + +## Env (`.env.local`, copy from `.env.example`) + +EmailJS (`NEXT_PUBLIC_EMAILJS_SERVICE_ID`/`_TEMPLATE_ID`/`_PUBLIC_KEY`, +`NEXT_PUBLIC_EMAILJS_CONTACT_TEMPLATE_ID`) — without these the contact form renders but won't +send. `NEXT_PUBLIC_RECAPTCHA_SITE_KEY`. `NOTION_API_KEY` (flight-data sync). +`NEXT_PUBLIC_SITE_URL` / `SITE_URL`. `NEXT_PUBLIC_CHAT_API_URL` (ChatBot widget only renders when +set). GoatCounter analytics: `GOATCOUNTER_API_KEY`, `GOATCOUNTER_SITE`, +`NEXT_PUBLIC_GOATCOUNTER_SITE`. `UPTIMEROBOT_API_KEY` (dashboard). `RESUME_SOURCE` (optional, +defaults to `~/personal/career/Mangonon_Dominic_Resume.html`). diff --git a/CLAUDE.md b/CLAUDE.md index a7d5d88..43c994c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,129 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -Dom Mangonon's personal site: a **single-page** portfolio built with Next.js 16, React 19, -TypeScript, and Tailwind CSS 4. Static export (no server runtime), deployed to GitHub Pages -at https://dommango.github.io. - -The page leads with the **project portfolio**, then writing, with career compressed to context. - -## Commands - -```bash -npm run dev # Start dev server (localhost:3000) -npm run build # Build static site (output: out/) -npm test # Run Vitest tests (watch mode) -npm test -- --run # Run tests once -npm run lint # ESLint check -npx tsc --noEmit # Type check -npx playwright test # E2E -``` - -Note: `npm install` needs `--legacy-peer-deps` (react-simple-maps declares a React <19 peer). -CI uses it too. - -## Architecture - -The whole site is **one page**. `app/page.tsx` is a server component that loads data at build -time and hands it to `components/landing/BrutalistLanding.tsx`, a client shell holding theme + -scroll-spy state and composing every section. - -``` -app/ -├── layout.tsx # Root layout, fonts, metadata/JSON-LD, ChatBot (only if NEXT_PUBLIC_CHAT_API_URL is set) -├── page.tsx # Loads projects/writing/travel data -> BrutalistLanding -├── globals.css # All styles (see Design System below) -└── dashboard-m7x9k2/ # Private-ish analytics dashboard (obscure URL, not linked) - -components/landing/ # The page, in DOM order: -├── Nav.tsx # Sticky nav + theme cycler -├── Availability.tsx # Status strip under nav -├── Hero.tsx # Headline, portrait, bio -├── Projects.tsx # THE MAIN SECTION — project cards -├── Writing.tsx # Substack posts; renders only when posts exist -├── Resume.tsx # Compressed career timeline -├── Travel.tsx # Continent bars + globe -├── Contact.tsx # EmailJS-wired form -├── Footer.tsx -└── BinaryRule.tsx # Decorative divider (seeded PRNG — see below) - -components/travel/TravelMap.tsx # Heavy globe, dynamically imported by landing/Travel -components/chat/ChatBot.tsx # Assistant widget -components/ui/ # Card, Skeleton, ProgressBar — dashboard only - -lib/content/ # Build-time data (no fs, no runtime fetch) -├── projects.ts # Hand-authored PROJECTS array -├── writing.ts # Substack posts -└── travel.ts # Transforms content/travel/*.json -lib/services/ # emailjs.ts, chat.ts -``` - -## Adding or changing a section - -Four places must stay in sync or the scroll-spy breaks silently: - -1. `components/landing/
.tsx` — the component -2. `SectionId` union in `Nav.tsx` -3. `SECTION_IDS` in `BrutalistLanding.tsx` — **must match DOM order**; the spy takes the last - element with `offsetTop <= scrollY`, so wrong order = wrong active link -4. The `link()` calls in `Nav.tsx` - -Conditional sections (Writing) need the nav link gated by the same predicate as the section, -or the link scrolls to nothing. - -## Design System - -All styles live in `app/globals.css` (~1130 lines). Two disjoint token systems: - -- **`:root` (lines 1-121)** — legacy tokens + Tailwind bridge. Used by the dashboard/ChatBot only. -- **`.brutalist-root` (128-239)** — the landing. Everything is scoped here. - -**Build new sections from semantic tokens only** — `var(--accent)`, `var(--fg)`, `var(--fg-muted)`, -`var(--fg-low)`, `var(--rule)`, `var(--s-N)` spacing, `var(--font-display|sans|mono)`. Do that and -all three themes (Gold / Oxblood / High Contrast) work for free, since the theme cycler only swaps -token values via `[data-accent]` / `[data-contrast]` attributes. - -The landing is **dark-only by design** — the `prefers-color-scheme: light` block only touches -`:root`, which `.brutalist-root` shadows. - -**Responsive: one breakpoint**, `@media (max-width: 900px)` at the bottom. Any new multi-column -grid must be added there manually; nothing is automatic. - -Structure convention: `.section` > `` > `.*-head`. The adjacent-sibling rule -`.section > .binary-rule + *` supplies the top margin, so keep that order. - -`BinaryRule`'s `seed` prop drives a deterministic sine-hash PRNG — it exists for **hydration -safety** (server and client must render identical digits), not aesthetics. Give new sections an -arbitrary unused seed. - -## Content - -Hand-authored TypeScript in `lib/content/`, not markdown. Prefer typed TS modules over JSON: -`resolveJsonModule` is on, and importing an empty `[]` JSON file infers `never[]`, which fails -typecheck under `strict`. - -Travel data is the exception — script-generated JSON in `content/travel/`, produced by -`scripts/process-travel-data.js` and `scripts/fetch-flights.js` (both manual). - -**Career content is deliberately NOT in this repo** — it's gitignored. This repo is public; the -source of record is `~/personal/career`. Don't re-add it or reintroduce a sync step. - -## Deployment - -`.github/workflows/deploy.yml` — on push to `main` (+ manual), builds and pushes `out/` to Pages. - -**Gotcha:** pushes made by `github-actions[bot]` with the default `GITHUB_TOKEN` do **not** -trigger workflows. So a cron that commits data cannot make the site rebuild. Anything needing -fresh data at deploy time must fetch **during the build**, not commit-then-rebuild. - -`update-dashboard-data.yml` crons analytics/uptime/performance JSON into `public/data/`. - -## Testing - -Vitest + jsdom for units (`__tests__/`), Playwright for E2E (`e2e/`). `vitest.setup.ts` mocks -`matchMedia` and `IntersectionObserver`. - -Test nontrivial logic (parsers, transforms, conditional-render invariants). Skip ceremony tests. +@AGENTS.md diff --git a/app/layout.tsx b/app/layout.tsx index d7da38c..934f657 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -50,7 +50,7 @@ export const metadata: Metadata = { template: "%s | Dom Mangonon", }, description: - "Dom Mangonon builds software with AI — SousIQ, Bracketeer, the Claude Code Placemat, and more. Projects, writing, and a travel map.", + "Dom Mangonon builds software with AI: SousIQ, Bracketeer, the Claude Code Placemat, and more. Projects, writing, and a travel map.", openGraph: { title: "Dom Mangonon", description: @@ -64,7 +64,7 @@ export const metadata: Metadata = { url: "/og.png", width: 1200, height: 630, - alt: "Dom Mangonon — builds software with AI", + alt: "Dom Mangonon: builds software with AI", }, ], }, diff --git a/components/landing/Hero.tsx b/components/landing/Hero.tsx index d6dbf84..568e318 100644 --- a/components/landing/Hero.tsx +++ b/components/landing/Hero.tsx @@ -37,7 +37,7 @@ export function Hero() {
About

- Seventeen years in financial services — operations at BNP Paribas through 2008, an MBA + Seventeen years in financial services: operations at BNP Paribas through 2008, an MBA at CMU Tepper, consulting at PwC, and now SVP at Citi.

diff --git a/components/landing/Resume.tsx b/components/landing/Resume.tsx index a8b69b4..d9bbdb7 100644 --- a/components/landing/Resume.tsx +++ b/components/landing/Resume.tsx @@ -6,7 +6,7 @@ const TIMELINE = [ { y: '2025 →', t: 'Building with AI', - d: 'Shipping software nights and weekends with Claude Code — see above', + d: 'Shipping software nights and weekends with Claude Code (see above)', }, { y: '2021 →', diff --git a/lib/content/projects.ts b/lib/content/projects.ts index b22a743..19d2a62 100644 --- a/lib/content/projects.ts +++ b/lib/content/projects.ts @@ -23,7 +23,21 @@ export interface Project { export const PROJECTS: Project[] = [ { - id: '#sous-0001/05', + id: '#plcm-0001/05', + name: 'Claude Code Placemat', + stack: 'Static HTML · GitHub Actions · scheduled agent', + year: '2026 →', + impact: 'Maintains itself · MIT', + points: [ + 'A one-page reference for Claude Code: shortcuts, slash commands, flags, hooks, MCP', + 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', + 'Nobody updates it by hand', + ], + href: 'https://dommango.github.io/claude-code-placemat/', + hrefKind: 'live', + }, + { + id: '#sous-0002/05', name: 'SousIQ', stack: 'Express · React 19 · Postgres + pgvector · Claude', year: '2025 →', @@ -37,32 +51,18 @@ export const PROJECTS: Project[] = [ hrefKind: 'live', }, { - id: '#brkt-0002/05', + id: '#brkt-0003/05', name: 'Bracketeer', stack: 'Next 16 · Prisma 7 · Auth.js · Railway', - year: '2026 →', - impact: 'Live · ran a real World Cup pool', + year: '2026', + impact: 'Ran a real World Cup pool · 40+ players', points: [ 'Create a pool, invite friends, make bracket picks, watch a leaderboard update from live results', - 'Knockout seeding follows FIFA Annex C — the tiebreak rules are genuinely gnarly', + 'Knockout seeding follows FIFA Annex C. The tiebreak rules are genuinely gnarly', 'Started as a pool for friends, grew into a multi-tenant platform', ], - href: 'https://fifawc26.up.railway.app', - hrefKind: 'live', - }, - { - id: '#plcm-0003/05', - name: 'Claude Code Placemat', - stack: 'Static HTML · GitHub Actions · scheduled agent', - year: '2026 →', - impact: 'Maintains itself · MIT', - points: [ - 'A one-page reference for Claude Code: shortcuts, slash commands, flags, hooks, MCP', - 'A scheduled agent re-reads the latest release every day and opens a PR when anything drifts', - 'The interesting part is not the page — it is that nobody updates it by hand', - ], - href: 'https://dommango.github.io/claude-code-placemat/', - hrefKind: 'live', + href: 'https://github.com/dommango/bracketeer', + hrefKind: 'repo', }, { id: '#modm-0004/05',