A production portfolio that proves static-first back-end craft: server-rendered pages, layered bot protection on contact, and an optional terminal overlay that ships real engineering, not just CSS.
Problem
Most portfolios optimize for visuals and hide how things are built. I needed a site that is fast on first paint, cheap to operate, honest about trade-offs, and deep enough that a back-end or fintech reviewer can see real system thinking—including where money-adjacent patterns (idempotency, abuse resistance) show up.
What it is
A server-rendered personal site and living engineering notebook. The same codebase hosts project case studies, a static-first blog, a production contact form, and an optional retro-style terminal console overlay. The terminal is enhancement-only: every route works with JavaScript disabled; the overlay lazy-loads on first `c` and can run a canvas snake game without polluting the initial bundle.
Constraints
- No database or CMS—content is Python data structures reviewed in git.
- Minimal JavaScript: zero terminal/game code on first load; lazy-load per feature.
- Contact must resist bots without storing submissions in-app (mailbox is the record).
- Deploy footprint must stay small (Render web service + Cloudflare DNS/TLS).
- Terminal must not hijack keyboard focus outside the overlay or break form inputs.
Architecture
Flask serves Jinja templates with shared layout and theme tokens in CSS. `PROJECTS` and `POSTS` in `app.py` drive cards, detail pages, and a JSON `site_index` embedded for the terminal. Contact posts to a Flask route that verifies Turnstile, applies per-IP rate limits, checks a honeypot, and sends mail via Postmark. The terminal loads from `static/js/terminal.js` on demand; games use a small host under `static/js/terminal/games/` with one script per title.
- Runtime: Python 3.12, Flask, Gunicorn (`Procfile`), `/health` JSON probe
- Hosting: Render web service; Cloudflare for DNS, TLS, Turnstile widget
- Content: `PROJECTS`, `POSTS`, `SITE_PAGES` in `app.py`; templates in `templates/`
- Contact: `POST /contact` → Turnstile verify → Flask-Limiter → Postmark API
- Terminal (Ship A): overlay UI, command parser, virtual cwd, `localStorage` layout + theme
- Games (Ship B1): `KekoGameHost` + `snake` factory; full-body canvas; wrap-around edges
- Docs: ADRs `0001`–`0009`, deploy/testing checklists under `docs/`
Key decisions
- Flask/Jinja over SPA—first render is HTML; no client router or hydration tax.
- Postmark API instead of SMTP on Render—fewer moving parts, better deliverability.
- Layered abuse controls: Turnstile (when enabled), IP rate limits, honeypot silent drop.
- Terminal lazy-loaded on `c`—baseline pages stay free of console logic.
- Game host interface + per-game scripts—Ship B2/B3 can ship without refactoring Ship A.
- Themes via `data-theme` on `<html>` and CSS variables—terminal `theme set` persists.
- Architecture decisions recorded as short ADRs, not wiki sprawl.
What shipped
- Phase 1: deployable scaffold, `/health`, `runtime.txt`, Render + Cloudflare wiring.
- Phase 2: home, projects, blog, about, contact shell; matrix-green design system; project detail template with pager.
- Phase 3: Postmark-powered `POST /contact` with Reply-To visitor email; Cloudflare Turnstile (managed mode); Flask-Limiter (per-IP minute/hour caps); honeypot field; env-gated local dev skip.
- Phase 3 docs: ADRs for Postmark, Turnstile/rate-limit, deploy and testing checklists.
- Phase 4 Ship A: press `c` or navbar hint → draggable/dockable terminal; commands help, clear, close, history, ls, cd, open, projects, theme list/set; layout + theme in `localStorage`; `prefers-reduced-motion` respected.
- Phase 4 Ship B1: `snake` command → lazy-load game host + snake; full-terminal play mode; wrap-around walls; theme-accent rendering; score on game over; clean teardown on quit/close.
What next
- Phase 2.5: hero visual slidedeck (blocked on artwork; see `AGENTS.md`).
- Phase 4 Ship B2/B3: invaders and tetris via the same game host (separate ships).
- Ongoing: deeper case-study copy on other projects; blog posts as they ship.
Notes
- Try it live: press `c`, run `help`, `theme list`, or `snake`.
- Implementation checklists: `docs/testing.md`. Security/delivery ADRs: `docs/decisions/0005`–`0009`.