diff --git a/.agents/skills/portfolio-design/SKILL.md b/.agents/skills/portfolio-design/SKILL.md
index 77bf377..935e748 100644
--- a/.agents/skills/portfolio-design/SKILL.md
+++ b/.agents/skills/portfolio-design/SKILL.md
@@ -8,7 +8,7 @@ description: How Ryan designs. Use before changing style.css, page layouts in bu
## Principles
1. **Content-first, bare bones.** The design's job is to make the work legible, then get out of the way. No hero animations, no carousels, no decorative imagery.
-2. **Fast is a feature.** Every page is a single request: CSS is inlined at build time, there is **zero client-side JavaScript**, fonts are the system stack. Keep any page under ~30KB of HTML. If a change needs a client script or a webfont, it needs a very good reason.
+2. **Fast is a feature.** Every page is a single request: CSS is inlined at build time, fonts are the system stack, and client JavaScript is avoided by default. A tiny inline script is acceptable only for explicit user-controlled color-scheme preference, because it prevents theme flash and preserves a real light/dark toggle. Keep any page lean. If any other change needs a client script or a webfont, it needs a very good reason.
3. **Boring is deliberate.** One accent color, one column, generous whitespace. Novelty budget is spent on the writing, not the chrome.
4. **Both color schemes, always.** Light and dark are first-class via `prefers-color-scheme` and the token block at the top of `style.css`. Never hardcode a color in a component: add or use a token.
5. **Show, don't tell.** Every page carries at least one real image. Real screenshots first (previews, product shots). When no honest screenshot exists, generate an SVG artifact card with `scripts/artifact-cards.mjs`; cards state only facts already in the entry (real commands, real published stats). Never mock a product UI or fabricate a screenshot.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c40709e..a86cbc6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## 2026-07-13: Portfolio feedback readiness pass
+
+- Compressed the portfolio header into a single-line shell on mobile and desktop, added Resume and Contact navigation, and added a manual light/dark theme toggle.
+- Added a resume-style page with tighter career sections, proof points, and links back to work and contact.
+- Replaced public email links with a backend-managed contact form and a gateway `/api/contact` route that sends through Resend when `RESEND_API_KEY` and `CONTACT_TO_EMAIL` are configured.
+- Updated About contact copy so the public site no longer renders Ryan's email address.
+- Kept existing real screenshots where available and left generated artifact cards only where no honest screenshot exists.
+
## 2026-07-12: Deployed UI/UX polish pass
- Audited the live site text rendering and demo pages, then tightened the local portfolio UI before the next deploy.
diff --git a/LEARNINGS.md b/LEARNINGS.md
index b3a8b05..5c4b00a 100644
--- a/LEARNINGS.md
+++ b/LEARNINGS.md
@@ -1,5 +1,12 @@
# Learnings
+## 2026-07-13: Contact pages should hide owner email addresses at the rendering layer
+
+Context: The portfolio needed contact UX without exposing Ryan's personal email address in the HTML.
+Learning: A static portfolio can still keep the owner's address private by posting to a same-origin backend route and reading the recipient from server-only environment variables. The public form should collect the visitor's reply address, while the server uses `CONTACT_TO_EMAIL` and provider credentials such as `RESEND_API_KEY`.
+Evidence: Today's pass removed public `mailto:` links to Ryan's address, added `/contact/`, and routed submissions through `/api/contact`. The route returns a setup message when mail credentials are missing instead of leaking a fallback address.
+Use next time: Never solve portfolio contact by adding a visible owner email. Add or reuse a backend form route, document the server-only env vars, and make missing-provider behavior explicit.
+
## 2026-07-12: AEO works best when the page and schema say the same thing
Context: A repo-wide copy pass needed to improve search and answer-engine clarity without adding vague keyword stuffing.
diff --git a/gateway/server.js b/gateway/server.js
index 4b5a7d5..7821ecf 100644
--- a/gateway/server.js
+++ b/gateway/server.js
@@ -22,6 +22,7 @@ import { handleIsochronesApi } from './lib/isochrones.js';
const PORT = Number(process.env.PORT || 8080);
const JSON_BODY_LIMIT_BYTES = 16 * 1024;
+const FORM_BODY_LIMIT_BYTES = 32 * 1024;
const { apps } = loadApps(process.env);
// Only public-visibility apps appear in /api/apps and /healthz.
@@ -73,6 +74,99 @@ function sendRaw(response, statusCode, body, contentType) {
response.end(body);
}
+
+function readTextBody(request, limitBytes = FORM_BODY_LIMIT_BYTES) {
+ return new Promise((resolve, reject) => {
+ let body = '';
+ let bytesRead = 0;
+ request.on('data', (chunk) => {
+ bytesRead += chunk.length;
+ if (bytesRead > limitBytes) {
+ reject(Object.assign(new Error('Payload too large'), { statusCode: 413 }));
+ request.destroy();
+ return;
+ }
+ body += chunk;
+ });
+ request.on('end', () => {
+ if (bytesRead <= limitBytes) resolve(body);
+ });
+ request.on('error', reject);
+ });
+}
+
+function sendHtml(response, statusCode, body) {
+ applySecurityHeaders(response);
+ response.writeHead(statusCode, {
+ 'Content-Type': 'text/html; charset=utf-8',
+ 'Cache-Control': 'no-store',
+ });
+ response.end(body);
+}
+
+function contactResponsePage(title, message, statusCode = 200) {
+ return `
Lead developer experience and forward-deployed platform work across Code Assist, agent skills, evals, AI-native distribution, and the Geo Architecture Center.
+
Google Cloud
0→1 industry solution product and engineering lead · 2021 – 2022
Led Intelligent Product Essentials from zero to launch with GE Appliances in nine months.
Grew customer engineering from 1 to 15 as Mapbox crossed $100M ARR. Took Boundaries and Atlas to their first $5M ARR and led OSS partnerships with Uber's visualization stack.
+
Instabase and Caterpillar
Solution architecture and industrial IoT
Led solution architecture at Series-B Instabase. Earlier, built industrial IoT systems at Caterpillar with 3 US patents.