building my website with ai
how this website took shape in SvelteKit, with Claude, Gemini and relentless human quality assurance
My old portfolio was an embarrassing cliché. Near-black background, terminal green monospace
font, a blinking cursor in the hero, and mock terminal breadcrumbs
(~/showcase/cascade). No offense to those who do it this way, of course, but the thing
looked like it was generated in four seconds by someone who had never designed anything in
their life.
I wanted a site that looked like collage, since it's one of the design styles I love the most. Because I'm also crazy about performance and accomodating different devices and performance profiles, I picked SvelteKit due to its performance characteristics such as surgical DOM updates without a virtual DOM and negligible memory overhead on weaker mobile chips. By the way, this website should be super smooth even if you're on an older device, like the iPhone 6s I have here (and used extensively for testing).
So I opened an empty folder and spent the next few weeks building and improving it progressively with Claude and Gemini.
escaping the training set
If you ask an LLM to design an interface without guardrails, you get whatever sits at the center of its training data. Depending also on the biases of the company, this can result in purple gradients and bento grids, the dominant style across the web for the last few years. With Claude, the default leans toward an exaggerated editorial look: giant serif headlines, 1990s magazine typography, an earthy palette, and faux-artisanal touches.
To get off those defaults, I asked Claude to go online and research various schools of design and art. I specifically asked it to look at the work of artists and designers from different eras and regions. Then, it drafted standalone HTML boards to display redesign options: Curitiba's transit grid, risograph printing, concrete-art planes and a few others.
The one I decided to keep was the one Claude called "Antropofagia", based on Oswald de Andrade's 1928 manifesto of the same name: loosely, the idea of taking what you learn from elsewhere and making something of your own with it rather than copying it. That felt close to how the work actually happens for me: Swift learned from Apple, then design from a mostly European canon, then psychology from foreign books, and in the end I, a Brazilian, have to make things out of the lot.
In the SvelteKit components, that idea became literal. Instead of putting screenshots inside
fake browser frames, the headline letters swallow the newest project cover directly into the
typography with background-clip: text. The rest of the site stays quiet with its
newsprint and serif type, with the collage surfacing in just two other places: the section
titles and the torn halftone thumbnails.
For the project showcase grid, I also took inspiration from Molleindustria's website, which puts the studio's games in a grid arrangement with taglines that I've always appreciated.
An earlier draft stamped the homepage with Tupi or not Tupi — Andrade, 1928, but we cut it. It was kind of cringe.
AI doesn't have eyes, but it can use a ruler
The homepage's headline was one of the first places Claude led me straight into a wall. A broken line of code is easy to spot, but LLMs love writing valid CSS, giving a convincing explanation of how the thing works, and having no idea of basic things such as the fact that web browsers often work in unexpected ways.
Because the hero letters contain covers from different projects, they need a guaranteed
contrast floor so the words remain legible against the newsprint background
(#efe9e2). Claude combined background-color, a multiply blend mode,
and text clipping, assuring me the contrast would never drop below 3:1. In Chromium,
background-color is silently dropped from the blend group when text clipping is
active. Over a dark cover like Cascade's, the letters turned to mud, while browser devtools
reported styles that looked completely fine.
Arguing with an LLM in chat about visual bugs goes nowhere. It can't see the screen, so it
just doubles down on why its math should work. The only way forward was to build an
automated check. I directed it to write floor.js with Playwright and Sharp. It opens a headless
browser, takes a screenshot of the headline, trims the antialiased edges off each letter, and
checks every interior pixel. If any pixel drops below 3:1 contrast against the background, the
script exits with an error.
Once the model could run the script and see real numbers in the terminal, voilà, the problem was solved
in ten minutes. Linear gradients inside background-image stay in the blend group,
so layering a gradient under the cover clamped the lightness without touching
background-color.
Buttons on paper
We hit the same problem with button interactions. Most modern websites, particularly those made by LLMs, answer a hover by
inflating elements with scale(1.04). Real paper does not swell when a finger
approaches it. I wanted buttons that felt like scraps of paper sitting on newsprint: a 3px
hard cut edge underneath. When you click, the scrap sinks flush with the page.
When we first wired it up, half the buttons didn't budge. Entrance animations were holding
their keyframe transforms in place and overriding the click state. Moving the press offset to
the CSS translate property fixed it, since translate works
independently of keyframe transforms.
To keep that behavior from breaking later, Claude cooked up scripts/press.js. It runs
Playwright, holds the pointer down on every button across six routes in both themes, and
checks that the element actually moves 3px on click.
Certified ink
Models also have no sense of the whole project. When you ask AI to build or tweak a single component, it writes code that works fine on its own, but across multiple sessions it loses touch with the rest of the site.
On this site, for example, each section uses its own ink: charcoal for home, blue for showcase, green for
blog, and red for contact. Every link, hover state, and filter chip is supposed to inherit
that section's --accent color. But because a stylesheet with four colors still looks
reasonable at a glance, bugs slip through. For weeks, the blog index had a blue back-link and a
black filter chip, until it came up on the priority list to be fixed. The model never had any idea it was wrong.
To stop that drift, you guessed it, more scripts were written: colors.js and look.js.
Instead of checking screenshots by hand or re-reading CSS, the scripts inspect the live DOM in a
headless browser. They print a table of computed values, verify that every element uses the
right section ink, and measure column widths. If a route borrows a color from
another section, the test fails.
Fitting words
The site is bilingual, which brought a layout problem. Portuguese words run noticeably longer
than English ones. DEVELOPER has nine letters; DESENVOLVEDOR has
thirteen. If you pick a fixed font size or guess with breakpoints, one language will sometimes overflow the
column or the other will look loose.
Rather than guessing font sizes or breakpoint values, I got Claude to write a small Python script with
fontTools to calculate the exact advance width of each headline word down to the
thousandth of an em. We stored that value as a custom property (--k) and used
container query units (cqi) to let the headline size itself to the column:
.head { --k: 15.56; font-size: calc(88 / var(--k) * 1cqi); }
Both languages lock to the column, preventing overflow.
Human brain, machine hands
People sometimes assume building a site with an LLM means you don't need to understand browser internals. In practice, you need to know them better. When a model gives you five plausible theories for why an animation or layout broke, you still have to know which layer of the system actually failed.
Working this way also changes how you prompt. Writing long paragraphs trying to talk a model into having taste or respecting physical constraints doesn't work. Instead of begging it to follow rules, you use it to write a script that exits non-zero when it doesn't. The prompt becomes basic logic: the test failed, here's the output, fix it.
In the end, the future has arrived. AI models bring a lot of speed. Claude refactored something like thirty components in one go when we renamed design tokens,
and later on Gemini tracked down why Mobile Safari needed an empty touchstart listener in
+layout.svelte just to register a click. But these things have no visual taste and no
resistance whatsoever to clichés. Every change still has to answer to a real situation, and it needs to be reviewed by a person with intention. I don't think that's going to change for a good while.