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, three 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 and one deferred script of under 500 lines, 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, with a palette borrowed from JetBrains Rider, and 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.
- 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.