Skip to content
All builds

Project Card Component

What it does for you

One pure projectCard(data) → HTML function that renders the whole portfolio from the case-study YAML contract.

Project Card Component screenshot

Screenshots

Project Card Component screenshot 1

In more detail

One card design meant to display every project in my portfolio. You hand it a project's details and it draws the card: cover image, project number and tier, title, a short blurb, tags, what the project proves, and links to the demo and code. The goal was 100 cards from one component instead of 100 one-off layouts.

Honest limits

The 14-assertion harness isn't kept in the folder and the tracker records no test count. The demo reads a hand-built array of three projects, not real case-study.md files, because there's no YAML parser in it. Three sample cards is a small sample.

How I built it

Claude Code, about an hour and a half. The key move was that Claude read the actual YAML block that #2's generator writes, plus #1's filled-in version, and built the component to that exact field set instead of inventing a new shape. The result is one pure function, projectCard(data), that takes data in and returns HTML.

I picked the three states it had to prove: a featured card with a demo, a plain card, and a card with no cover image. Claude wrote the render function and a demo page with three sample cards (two real shipped projects and one made-up one) in a responsive grid with a dark and light toggle.

The first pass had a tangled isLinkable() one-liner. Claude rewrote it as a readable hrefOf(). The Node test harness, which evals the page's own code, failed twice before it passed: once from a doubled = introduced by a string replace, then from const scoping inside eval. Appending the assertions into the eval'd script so they share scope fixed it, and it went 14 for 14.

What I learned

  • A reusable component is a contract. Render every field the producer emits, and add nothing it doesn't.
  • The hard part of a "simple" card is the edge cases: a missing cover, an empty demo link, a path with a human note stuck on the end like "(runs locally...)". The guard logic, hrefOf() and the hatched fallback tile, is the real product, not the markup.
  • You can test UI code without a browser by eval-ing the page's own render function in Node. The assertions run the exact code the user sees.

What carried forward

The plan was for projectCard to drop unchanged into the portfolio site (#96). I want to be straight about that: it didn't. When #96 got built in September, it had its own render code. What did carry forward was the contract. #96 still reads the same YAML metadata block this card was built around.

The testing idea carried forward too, in a cleaner form. Eval-ing a page in Node worked, but it was fiddly. By #9 and #10 we'd moved to a pure module that both the browser and a verify.mjs file import directly, which became the pattern for the rest of the sprint.

If I took it further

I'd add a real YAML parser so the demo reads live case studies, while keeping projectCard a pure data-in, HTML-out function.

#003 Built with Claude Code June 22, 2026

Want one like this?