A footnote for the curious. The whole site follows the KISS principle, keep it simple: there is no server behind this page, only static files on a CDN, four Lambdas, and a CloudFront Function.
- Front end
- I kept the front end simple: plain HTML, CSS, and vanilla JavaScript. No framework, no build step, no bundler, and no dependencies: one stylesheet, a short palette override, and one deferred script, shipped exactly as written. The Experience timeline is in the HTML, so it reads without JavaScript; when the Experience API answers, the script re-renders it from the JSON and reveals a skill filter.
- Styling
- Mobile-first CSS built on custom properties. Light and dark themes follow the operating system through
prefers-color-scheme until a visitor picks one with the toggle beside the edition menu. A few lines of script in the head apply a saved choice before the first paint, and every color follows it through color-scheme and light-dark(). The shared palette borrows from JetBrains Rider; this edition warms it to eggshell through a small file that overrides only the design tokens with light-dark(), so no rule is repeated. prefers-reduced-motion switches off the transitions. Inter and JetBrains Mono are self-hosted as variable WOFF2 subsets and preloaded. Icons are a single inline SVG sprite.
- Markup
- Semantic landmarks, a skip link, ARIA labels, and visible focus rings for keyboard and screen-reader users. Search engines get JSON-LD structured data, Open Graph tags, a canonical URL, and a sitemap.
- Hosting
- The files sit in a private Amazon S3 bucket that only CloudFront can read, through Origin Access Control. CloudFront serves them over HTTP/2 and HTTP/3 with an ACM certificate, Route 53 aliases the domain to the distribution, and a CloudFront Function redirects
www to the apex.
- Security
- A CloudFront response headers policy sets a strict Content Security Policy (same-origin only, no inline script or style, nothing third-party), two-year HSTS,
X-Frame-Options: DENY, nosniff, and a locked-down Permissions-Policy. The site sets no cookies and runs no analytics.
- Contact form
- Posts JSON to an AWS Lambda function URL (Node.js 22 on arm64) that emails the message through Amazon SES. Spam is filtered before anything is sent: an origin check, a honeypot field, a minimum fill time, a per-IP hourly budget, and length caps on every field.
- Resume link
- The PDF is kept out of the public repository. The download button goes to a second Lambda that publishes a notification to an SNS topic, then answers with a
302 to the file in S3. If the notification fails, the visitor still gets the resume.
- API integration
- The work history above is served by an API at
/api/experience, with a skill filter and an aggregated /api/skills view. It is C# on .NET 10, run on AWS Lambda (arm64) behind this same CloudFront distribution and reading a single DynamoDB table. The OpenAPI contract was written and committed before any code. Responses carry ETags and cache headers so CloudFront answers most requests itself, errors follow RFC 9457 problem details, and the function URL accepts only requests signed by CloudFront. Write endpoints are specified and stubbed for an admin API to come. The contract is public: OpenAPI spec.
- Healthcare demo
- The FHIR API card in Selected Work opens the live FHIR R4 patient explorer at fhir.mvcprogrammer.com, an Angular app of its own, in the same native
<dialog> the kiosk demo uses: main.js creates the iframe when a demo button is pressed and removes it on close, so the explorer and the C# Lambda behind it load only when a visitor asks. The function reads a public sandbox of synthetic records through the Firely SDK, maps the resources to small DTOs, and reports every upstream call so the explorer can show what happened; its write side creates, replaces, patches, and deletes demo patients under a guard tied to the visitor's session. Nothing is stored, and no real patient data is involved.
- Deploys
- A GitHub Actions workflow runs on every push to
main. It assumes a least-privilege IAM role through OIDC, so no AWS keys are stored in GitHub, runs aws s3 sync --delete in two passes (a week of browser caching for fonts and images, five minutes for pages), and invalidates the CloudFront cache. The third-party actions are pinned to commit hashes.
- Tooling
- Written in JetBrains Rider with Claude Code as a pair programmer, and previewed locally with
python3 -m http.server before each commit.