How this site is built
This site is a static Astro build with React islands, deployed on Cloudflare Pages. The resume is data, not markup: one YAML file drives the landing page, the resume page, the D3 visualizations, the ATS PDF, and the chat assistant.
Ask the chat assistant about any of this — it knows the same list.
Framework & UI
Static HTML by default; interactivity is opt-in per component.
- Astro 7
- Static site generator. Ships zero JavaScript unless a component asks for it.
- React 19
- React and react-dom, used only for interactive islands - visualizations, chat, theme and motion toggles.
- TypeScript
- Strict mode across the site, the build scripts, and the Cloudflare function.
- Tailwind CSS 4
- Utility styling over a small set of CSS custom properties for theming.
- Fontsource
- Self-hosted Inter, Fraunces, JetBrains Mono, and Caveat. No third-party font requests.
Data Visualization
Three views of the same skills data, each a separate island.
- D3
- Force-directed skills constellation, radial bars, and treemap.
- Rough.js
- Hand-drawn rendering style for the hero field.
AI Assistant
The chat can highlight skills and roles on the page while it answers.
- Claude Opus 5
- Answers visitor questions about the resume and about this site.
- Anthropic TypeScript SDK
- Calls the Messages API from a Cloudflare Pages Function.
- Tool use
- Four tools let the assistant highlight skill groups, spotlight a role, highlight job-description matches, and switch theme.
- Two-turn tool loop
- Tool results go back to the model so every reply contains a written answer, not only a highlight.
- Streaming (NDJSON)
- Replies stream token by token over newline-delimited JSON, so text appears as it is generated. NDJSON rather than SSE, because the browser must POST and EventSource cannot.
- Prompt caching
- The ~16K-token system prompt is byte-identical on every request and cached, which cut the cost of a reply from about $0.098 to $0.024.
- Usage telemetry
- Every reply reports its own tokens in and out, cache hit rate, and cost in USD, computed from the API usage fields.
- Deep links
- Each highlight resolves to an anchor on /resume, so the visitor can jump straight to what the assistant pointed at.
Privacy & Observability
Conversations are logged anonymously. No IP address and no user agent are stored.
- Cloudflare D1
- SQLite at the edge. Stores one row per chat exchange with a random session id, country, tokens, and cost.
- Cloudflare Access
- Zero-trust policy in front of the private /admin log view. There is no application password to leak.
- 90-day retention
- Rows older than 90 days are purged on write, so nothing accumulates indefinitely.
- Anonymous by design
- IP address is personal data under GDPR and CCPA, identifies little behind corporate NAT, and is deliberately never collected.
Hosting & Deployment
A push to the main branch is the deploy. There is no staging.
- Cloudflare Pages
- Static hosting, git-connected build from the main branch.
- Cloudflare Pages Functions
- Serves the /api/chat endpoint on Workers.
- Wrangler
- Cloudflare CLI and project configuration.
- GitHub Actions
- Runs typecheck, unit tests, browser tests, and the ATS PDF check on every push.
Content Pipeline
One YAML file is the source of truth for every surface, including the PDF.
- YAML
- content/resume.yaml holds the resume; content/stack.yaml holds this page.
- Zod
- Validates both files at build time, so a malformed edit fails the build.
- tsx
- Runs the TypeScript build scripts directly.
- Playwright (Chromium)
- Prints /resume-print to the ATS PDF that visitors download.
- pdf-parse
- Verifies the committed PDF still matches the resume data, including a content hash.
Testing & Accessibility
The site targets WCAG AAA contrast, and the tests enforce it.
- Vitest
- Unit tests for the resume schema, chat helpers, and contrast math.
- Playwright
- End-to-end tests for the landing page, resume page, career explorer, and chat.
- axe-core
- Automated WCAG 2 A, AA, and AAA checks on every page and on the open chat widget.
- Reduced motion
- Every animation respects prefers-reduced-motion, with a manual toggle as well.