The build
I didn’t learn
to code. I ran
a project.
Evenings, start to live
Total spend — one domain, everything else on free tiers
Lines of code I wrote myself
I’ve been a program manager in tech for more than ten years. Cybersecurity, streaming, logistics, defense, retail. The job has never been one thing. You wear whatever hat the week requires and you get your hands dirty, because the alternative is standing next to a problem and describing it.
So when I started looking for my next role, the obvious move was to polish the LinkedIn profile and start applying. I did that. It’s fine. It works.
But everyone has a LinkedIn profile, and now everyone has AI too. The tools that used to separate people are sitting on every desk. Which means the thing that stands out isn’t having access to them. It’s what you actually do with them.
I thought: I can describe how I work, or I can build something and let it speak.
So I built this.
I wrote the spec before I wrote anything else
The instinct in a project like this is to open a tool and start making things. I’ve watched that go wrong for a decade.
Instead I spent the first hours on the questions I’d ask any team. What is this actually for? Who is the audience? What does success look like? What is explicitly out of scope?
The answers narrowed things fast. The audience is hiring managers and recruiters at large tech companies. Success is one real conversation, not a thousand visitors. Out of scope is every feature that makes a portfolio site feel like a portfolio site.
I wrote all of it down before any code existed — data model, page inventory, security requirements, build order, the things I was deliberately not doing. Then I handed that document to the AI as the source of truth.
That last part mattered more than I expected. Every decision after that got measured against a written standard instead of against whatever I felt like that hour. It’s the same reason I make teams write things down.
I decided what the site had to prove
A portfolio tells you someone is competent. I wanted something that demonstrated it instead.
So this isn’t a document about my work. It’s a system: a gate, a session, an event trail, an analytics dashboard. If you request access and get a code in your inbox, that’s a thing I specified, sequenced, and shipped — which is a stronger claim than any bullet point I could write about being detail-oriented.
That one decision drove everything else. It’s why there’s an access gate instead of an open page. Not to be precious about the content. To have something real to build.
I picked boring infrastructure on purpose
Domain, hosting, database, transactional email, source control. Five decisions, each with tradeoffs, each made once and documented.
I wanted to try the boring, correct version of every one: managed hosting, managed Postgres, a real email provider with proper domain authentication. No clever choices. Clever choices are how side projects die three weeks in, and I had three evenings.
Total cost was $11 for the domain. Everything else runs on free tiers that will hold at the traffic this site will ever see. I checked that before I committed to any of it, because “what happens when this scales” is a question I’d ask a vendor and I wasn’t going to skip it on my own project.
I built the gate myself instead of buying one
There are services that do email-code authentication for a few dollars a month. I read their docs and then decided against them.
The reasoning: the gate is the part of this site that most directly demonstrates the skill. Buying it would have saved a few hours and removed the entire point. So I built it — one-time codes that expire in ten minutes, single use, five attempts before the code dies, rate limits per email and per IP, and a session that runs out on its own. It is the thing you went through to read the portfolio.
I thought about the failure modes before writing any of it. What happens when someone requests five codes? What happens when a code is used twice? What happens when someone’s session expires mid-page? Writing those down first is why the build went quickly. Most of what slows a project down is discovering requirements late.
I designed by subtraction
Two design directions came back. One was safe, one was ambitious. I took the ambitious one and then spent most of my effort removing things from it.
The accent color appears exactly twice on the homepage. That was a rule I set early and then had to defend three separate times, including once against myself when I wanted a third use. A constraint is only real if you enforce it when it’s inconvenient.
I shipped in order and verified every step
Public page first. Then the gate. Then the content behind it. Then tracking. Then the dashboard.
Each step got checked before the next one started — certificates, metadata, the mobile layout at 375px, whether every link actually resolved. I wanted the site live and correct at every stage rather than perfect and theoretical at the end.
I found real problems this way. A certificate that covered one hostname but not the other. An image reference that would have broken every time the link was shared. Small things, all of them cheap to fix at the time and expensive to fix later. That’s the entire argument for verifying as you go.
What testing actually caught
Four things went wrong that nothing would have flagged on its own.
The access gate returned the same message whether a code was sent or silently rate-limited — but the sent path did real work and the blocked path returned instantly. Identical text, different timing, and the difference is measurable. That’s enough to turn the endpoint into a way of testing whether an address is in the database. Fixed by returning first and doing the work afterward.
The admin dashboard was reachable by any visitor holding a valid portfolio session. Both tokens were signed with the same secret, so copying one cookie’s value into the other’s name got past the check. It never shipped — it was caught by testing whether a portfolio session grants admin access, which was on the list precisely because the answer isn’t obvious.
The print stylesheet silently collapsed every tab. A CSS rule in a layer beat an unlayered one, nothing errored, and the page just quietly printed wrong. Recruiters print these.
A single git add -A staged an entire nested repository as a broken pointer. It would have cloned as an empty folder and nobody would have noticed for weeks.
None of these threw an error. Three of the four looked like they worked. That’s the whole argument for verifying each step instead of building the thing and checking at the end.
The part you can’t see
Every visit to the portfolio writes an event — which role tab opened, whether the resume downloaded, how long the session ran, whether someone came back. The dashboard reads that history. Repeat visitors sit first because a second visit is the strongest signal this site will ever produce.
It’s owner-only, and it returns a 404 rather than a redirect when you aren’t signed in, because a redirect confirms the route exists.

About the AI
I didn’t write the code. I want to be direct about that, because the interesting part isn’t whether I can type TypeScript.
I directed it. I wrote the specification, made the architecture calls, reviewed what came back, caught what was wrong, and decided what shipped. When it put content on the wrong page, I moved it. When it added a third use of the accent color, I cut it. When it told me I’d exposed a database credential, I rotated it — which is worth including, because a build story with no friction in it isn’t believable.
That’s the same job I’ve done for ten years. Scope the thing, sequence the work, hold the standard, own the outcome. The team is just different.
“He helped raise the bar for how AI should be used inside engineering, not as a novelty, but as a practical execution partner.”
Where it stands
The dashboard is live — it shows me who requested access, what they read, how long they stayed, and who came back. That last number is the only one that matters.
Personalized invite links came next — a single URL that takes someone I’m reaching out to straight into the portfolio with no code and no email. Email is a dependency I didn’t want on the critical path for the people I most want reading this.
One thing I got wrong: I assumed a verified sending domain meant mail would arrive. The first codes went to spam at Gmail and Outlook both. The cause was a missing DMARC record I thought I’d already set — the fix took ten minutes and finding it took two days. Codes land in the inbox now, which I verified rather than assumed.
Built in public, on purpose.