This Portfolio
Design Systems & Front-End Build
Building this portfolio with AI, and learning where it actually helps.
I designed and built the site you are reading with AI in every seat: as a thought partner for choosing a stack, as a design tool, and as the engineer that wrote most of the markup. Two full design attempts and six weeks later, the useful lesson was not about prompting.
The work that mattered turned out to be writing things down. A markdown file of design tokens and a reusable section prompt did more for the build than any single generated component.
Stack candidates
Design passes
Build time
Hand-written CSS
3
2
5 wks
~5%
frameworks weighed against the same written brief before committing to one.
attempts at the visual language; only the second survived contact with code.
from an empty repository to a deployed site, on nights and weekends.
of the final stylesheets typed by hand rather than generated from a spec.
Step 01 · Stack
Using AI as a thought partner, not an oracle.
My first instinct was to ask which framework was best, which produced exactly the listicle that question deserves. The second attempt worked: I wrote down my constraints and asked for the argument both for and against each candidate.
The brief I gave it
A static portfolio, image-heavy, no CMS, cheap to host, and still editable in a year by a designer who writes some code. Argue both sides of every option.
Three candidates, one brief.
Forcing a case against each option was the part that changed the outcome. The framework I would have reached for by default lost on a constraint I had written down myself.
Next.js
PassedThe strongest case on paper, and the one I expected to pick. Pushed on the brief, it conceded that a React runtime, a server, and a hydration story bought nothing for a site with no dynamic data and no logged-in state.
Astro
ChosenShips no JavaScript unless a component asks for it, treats sections as static markup, and lets interactivity opt in island by island. The trade-off is a smaller ecosystem and fewer answers when something goes wrong — acceptable at this size.
Hand-written HTML & CSS
PassedThe most durable option, and genuinely tempting. But a shared nav, footer, and card would live in every file by copy-paste. Fine across five pages, a maintenance tax by fifteen.
Stack comparison
Choice made
Astro 4 with Tailwind and TypeScript, built to static files and deployed from Git.
Lesson learned
AI is most useful when it has constraints to push against. An open question returns a survey of options; a written brief plus 'argue both sides' returns a decision I could still defend six weeks later, when changing it would have been expensive.
Step 02 · Deploy
Deploy the empty thing first.
Before a single design decision existed, I generated a blank Astro app, pushed it to GitHub, and wired continuous deployment to a live URL. For the first day the site was public, functional, and completely boring.
Why an empty deploy earns its day.
Repo and deploy pipeline
Choice made
Static output, Git-based continuous deploy, and main as the only long-lived branch.
Lesson learned
Infrastructure first is not a detour. The empty deploy became the safety net for every risky prompt afterward — when generated code broke the build, I knew the break was mine and recent, which made it a two-minute problem instead of an investigation.
Step 03 · First Attempt
Designing in Figma, prototyping with Figma Make.
The first pass followed the familiar path: references, a type scale, a handful of desktop frames, then Figma Make to turn those frames into something I could actually click.
What the prototype was genuinely good for.
Clicking through it answered questions static frames could not. A hero that looked balanced standing still fell apart the moment the page scrolled under it, and the nav needed a translucent state I had not thought to draw. Both problems surfaced in minutes.
Figma frames
Figma Make prototype
Where it broke down.
The prototype was a good answer to the wrong question. Three problems only became visible once I tried to turn it into a real build.
A prototype is not a specification.
Figma Make produced something convincing to click through, but the code behind it was not code I wanted to own. Handing it to an agent as a reference produced markup that matched the picture and quietly ignored every convention I cared about.
Desktop-only frames hid the hard part.
Three polished frames at 1440px said nothing about how a four-column metadata grid should behave at 700px. Every responsive decision got deferred to implementation, which is where deferred decisions turn into guesses.
The reasoning stayed in my head.
The frames showed a 30px semibold heading. They never said 'section heading'. Without a name for the role, the agent had to infer intent from pixels, and it inferred differently each time it looked.
Choice made
Keep the Figma pass as exploration and throw the prototype away rather than build on top of it.
Lesson learned
A visual prototype answers 'does this look right'. It cannot answer 'what are the rules', and implementation only needs the rules. Pixels are a lossy way to carry intent to whoever builds next — including an agent.
Step 04 · Second Attempt
Designing in the language the build would speak.
The second pass used Claude Design and inverted the order of work. Instead of drawing screens and deriving rules from them afterward, I settled the rules first: type roles, color tokens, spacing rhythm, and the page margin systems. Screens came out of the system rather than the other way around.
From pixels to named roles.
Every typographic decision got a name, a Tailwind class list, and a note about where it applies. The table below is the artifact that replaced my Figma type scale — and the one the agent actually read.
| Role | Tailwind classes
|----------------------|------------------------------------------------
| Case study title | text-5xl sm:text-6xl md:text-[64px] font-bold
| Opening paragraph | text-2xl leading-relaxed
| Header / subheader | text-base font-semibold
| Section label | text-sm uppercase tracking-widest text-secondary
| Body copy | text-sm leading-relaxed
| Caption / muted | text-xs text-muted Deciding the awkward things on purpose.
The valuable entries were not the type sizes. They were the decisions I would otherwise have improvised forty separate times: that the homepage uses flat 88px margins while case study prose sits inside a narrower pair of guide lines, that decorative grid lines sit at one paint layer and opaque surfaces at another, that a hairline is 2% black and stays that way.
Each of those started as a correction I had made twice. Writing it into the token file with the reason attached is what stopped it becoming a third.
Design tokens and margin systems
Choice made
One markdown token file as the single source of truth, checked into the repo beside the code it governs.
Lesson learned
Writing the system down changed who could enforce it. The same document is documentation for me and a constraint for the agent, which means the effort of maintaining it gets paid back twice. Design decisions that live only in a Figma file cannot do that second job.
Step 05 · Build
Design output as a prompt, not a picture.
The handoff between design and code turned out to be the real design problem. Claude Design's job was not to emit components — it was to emit a brief precise enough that Claude Code could build a section without having to guess what a heading meant or where a margin came from.
Anatomy of the section prompt.
The template settled into six blocks: stack, scope, component granularity, deliverable format, workflow, and constraints. Scope and deliverable format did most of the work, because they are where ambiguity would otherwise survive.
SCOPE
- Fidelity target: [pixel-exact desktop] / [close approximation]
- Interactivity to port: [specific behaviors, or "static markup only"]
- Responsive behavior: [breakpoints and what changes at each]
- Images: [real assets at these paths] or [PlaceholderImage.astro]
DELIVERABLE FORMAT
- Exact copy (verbatim)
- Text styling as design-token roles, not raw values
- Colors as token names (bg-surface, text-secondary), not hex
- Spacing using the vertical rhythm already defined in tokens One section per conversation.
Each section got its own prompt, its own commit, and a gate before the next one started: render it, screenshot it, compare it against the reference, approve or revise. Batching four sections into one request was faster to type and consistently slower to finish, because a wrong assumption in the first section had already been copied into the other three.
Prompt to rendered section
Choice made
One section per prompt, with a screenshot-and-approve gate before moving to the next.
Lesson learned
The prompt template became the most reused artifact in the project. Generating code was never the expensive part — re-explaining the same conventions was, and a template is how you stop paying for that repeatedly.
Step 06 · Fine-Tuning
What the agent got close, and I had to finish.
Generated sections landed somewhere around 85% complete, and the remaining 15% was remarkably predictable. Five patterns accounted for nearly all of it. Drag a frame to move between versions.
Choice made
Fix the class of problem in the token file rather than the instance in the component.
Lesson learned
Fine-tuning was mostly a documentation exercise wearing a coding disguise. Every correction I made twice was a rule I had failed to write down, and editing the spec was consistently faster than editing the next section that would have repeated it.
Next Steps
What this build still owes.
Three things I would action before calling the site genuinely finished.
Reflection
Final takeaways on building with AI.
I expected this project to teach me about prompting. It mostly taught me about specification. The tools were capable enough that the limiting factor became how clearly I could state what I wanted — which is the same skill that makes a design system work between two people.
The bottleneck moved, it did not disappear.
Generating a section took minutes. Deciding what the section should be, and judging whether the result was actually right, took the rest of the week. AI compressed the part of this work I was already fastest at and left the slow part untouched.
The specification was the real deliverable.
The most valuable file in this repository contains no code. A token document, a set of conventions, and a prompt template outlasted every individual component the agent wrote — several of which were replaced twice.
Two attempts was the right number.
The Figma pass looked like wasted effort until the second attempt reused everything it taught me about hierarchy, rhythm, and what the page needed to do while scrolling. The mistake would have been committing to the first prototype, not making it.
Taste is not delegable.
The agent matched references accurately and followed written rules well. It had no opinion about whether a reference was worth matching, or whether a section deserved to exist. Every judgment that actually shaped the site was still mine to make.
Thank You
Built on other people's work.
Thank you to the maintainers of Astro and Tailwind CSS, whose defaults are good enough that most of my decisions were about restraint rather than repair. Thank you to the designers whose case study typography I studied closely enough to calibrate my own type scale against — the influence is deliberate, the fonts are my own.
And thanks to everyone who read a half-built section and told me plainly that the copy was not working yet. They were right every time.
Next case study