A Client Paid $12k for a "Custom" Site. I Opened DevTools and Found Lovable. (A Forensic Story)
A founder forwarded me his agency's $12,000 "fully custom build," proud as a new parent. Three seconds in, something was wrong. Twenty minutes in DevTools later, I had the receipt: a __Inter hashed font, a computed #3b82f6, and a data-v0 attribute nobody bothered to delete. Here is exactly how the autopsy went, and the conversation I had to have afterward.
The email arrived on a Tuesday with the subject line "WE'RE LIVE 🚀" and three exclamation points of genuine joy. Marcus — founder of a B2B scheduling startup, ten months of runway left, the kind of guy who answers Slack at 11pm — had just taken delivery of his new marketing site. His agency had quoted him twelve thousand dollars for a "fully custom, hand-coded, bespoke build." He'd paid it in two installments. The link was in the email. "Tell me what you think," he wrote. "I think it looks incredible."
I clicked. And before I'd read a single word of copy, before the hero image had even finished decoding, my stomach did the small drop it does when something is wrong and you don't yet know why.
The first three seconds
I want to be precise about this part, because the three-second reaction is the whole reason the rest of the story happened. I didn't *decide* the site looked AI-generated. I felt it, the way you feel a wrong note in a song you don't know. Then I had to spend twenty minutes proving to myself whether the feeling was real or whether I was just a cynic who'd seen too many landing pages.
Here is what my eyes caught before my brain caught up. The hero was a centered headline in a tight, even sans-serif over a faint blue-to-violet wash. Below it, three feature cards — identical width, identical rounded-2xl, identical drop shadow, each with a little line-icon in the top-left corner sitting in a soft tinted square. The CTA button was a solid blue pill that lifted a couple pixels on hover. A "trusted by" strip of grayscale logos. A bento-ish grid further down where one tile was twice as wide as the others for no reason the content justified.
None of that is *wrong*. That's the trap. Every one of those choices is defensible in isolation. But together, at a glance, they form a face I've now seen four hundred times — the face I wrote about in why every AI-generated website looks the same. The uncanny part wasn't ugliness. It was familiarity. This "custom" site looked exactly like a site I could generate myself, for free, in about ninety seconds.
So I had a hypothesis and a guilty conscience about it. Marcus was thrilled. I was about to potentially ruin his Tuesday. I owed him more than a vibe. I opened DevTools.
The autopsy begins
I started where I always start, because it's the fastest lie-detector on the web: the rendered fonts. F12, Elements panel, click the , jump to Computed, scroll to font-family.
font-family: __Inter_aaf875, __Inter_Fallback_aaf875, sans-serif;There it is. Not font-family: Inter — that would be a deliberate choice a designer makes and could defend. This is __Inter_aaf875, the hashed, locally-optimized font variable that next/font generates automatically. It's the fingerprint of a next/font/google import with Inter passed in, which is the default that v0, Lovable, and Bolt all reach for the instant you say the word "modern." A human typographer picks a typeface for a reason — a personality, a brief, a mood. This was the absence of a choice, dressed as a choice. I've argued before that Inter has become the font of having no opinion, and here it was, hashed and minified, billed to Marcus at twelve thousand dollars.
One data point isn't a case. I kept going.
Color, next. I clicked the CTA button and read the computed background-color. DevTools reported it in rgb, but the conversion is one I have memorized the way some people remember phone numbers:
background-color: rgb(59, 130, 246); /* = #3b82f6 */#3b82f6. Tailwind's blue-500. Not "a blue." *The* blue. The single most over-served accent color on the 2026 web, the one I keep telling people to walk away from in how to pick an accent color that isn't Tailwind blue. A studio that charges five figures and has a brand designer in the room picks a hue with intent — they'll land on a hue angle nobody else owns, a teal at 178 or a clay at 22. They do not ship the literal default value of the most popular CSS framework on earth. The gradient behind the hero confirmed it: from-blue-500 to-violet-500, the blue-to-purple sweep that has become, functionally, a watermark for "an LLM made this."
Two fingerprints now. I stopped feeling guilty.
Reading the structure, not just the surface
Surface tells can be coincidence. A lazy human dev could grab Inter and blue-500 too. So I went into the DOM to read how the thing was *built*, because the way a page is assembled is much harder to fake than the way it looks. This is the method I lay out at length in reading the CSS to audit any site for AI tells — you stop looking at the design and start looking at the construction.
I expanded the feature cards in the Elements tree. The class string on each one was a forty-token Tailwind run, and all three cards carried the *byte-for-byte identical* string:
flex flex-col items-start gap-4 rounded-2xl border border-gray-200
bg-white p-6 shadow-sm transition-all duration-300 hover:shadow-md
hover:-translate-y-1transition-all duration-300 plus hover:-translate-y-1. The hover-lift micro-interaction that I've come to treat as an AI reflex — the exact pattern dissected in the hover-lift translate-shadow tell. A human building three cards by hand will, somewhere, let entropy in — a different gap on the middle card, an icon nudged two pixels, an off-by-one in the padding because they were tired. There's no entropy here. Three cards, one identical string, repeated. That's not a person being consistent. That's a generator emitting the same node three times.
Then the scroll behavior gave up another one. As I scrolled, each section faded in and rose a few pixels — the fade-in-up on intersection-observer that every generated site ships by default, the motion slop pattern, every element on the same 300ms ease-out, no stagger, no variation, no reason. Motion that exists because the model knew motion was expected, not because anything on the page needed to move.
I had a pile of circumstantial evidence now — enough to be confident, not enough to put in front of a founder who'd just paid for "hand-coded." Vibe plus Inter plus blue-500 plus identical cards is a strong case. But Marcus was going to push back, and I wanted something he couldn't argue with. I wanted the murder weapon, not the motive.
The smoking gun
I went to the Network tab and reloaded with the cache disabled. The JS bundles told the first hard truth:
/_next/static/chunks/app/page-8f3c2a1b.js
/_next/static/chunks/main-app-2d9e7f04.js
/_next/static/css/4a8c91e2b3d7.cssA /_next/static/ tree. Fine on its own — half the web is Next.js, and being a Next.js site proves nothing. But combined with the hashed Inter and the Tailwind-default palette, the picture sharpened: this was the exact stack that v0 and Lovable scaffold by default. Plausible, still deniable.
So I went hunting in the raw HTML. View Source, Ctrl+F. I searched the document for the strings these tools leave behind when nobody scrubs the export. data-v0. lovable. gpt. Made with.
The first two hits were on the :
<div id="__next" data-v0-t="card">data-v0-t. A v0 component-tag attribute. It does nothing in production. It's a build artifact that survives the export because removing it requires someone to actually read the markup, and nobody did. A from-scratch hand-build does not contain data-v0-t. There's no way for it to get there except through v0's pipeline. That single attribute is a confession.
And then — the one that ended it. Near the bottom of the , just before the closing tag, an HTML comment and a dead :
<!-- <a href="https://lovable.dev" ... >Edit with Lovable</a> -->
<!-- Made with Lovable -->The "Made with Lovable" badge. Someone had commented it out instead of deleting it. They knew it was there. They knew it would tell on them. So they hid it from the rendered page — and left it sitting in the source, in plain text, where the very first View Source reveals it. The ghost of the badge. The agency hadn't built a custom site and happened to use AI somewhere in the process. They had generated the entire thing in Lovable, exported it, swapped the logo and the copy, commented out the attribution so it wouldn't render, and invoiced Marcus twelve thousand dollars for "bespoke, hand-coded" work.
I sat there for a second. Not triumphant. The opposite. Because now I had to call Marcus.
The hard conversation
I didn't lead with the verdict. I screen-recorded the autopsy instead — me opening DevTools, clicking the headline, reading __Inter_aaf875, converting rgb(59, 130, 246) to #3b82f6 out loud, expanding the three identical cards, then the slow zoom to the commented-out Made with Lovable line. Six minutes. I sent it with one sentence: "Call me when you've watched this."
He called in eight minutes. The first thing he said was, "So it's not custom." Not a question.
"It's a Lovable export with your logo on it," I told him. "Which — and I need you to hear this part — is not the crime. The crime is the invoice."
This is the distinction I make every time, and it's the one in the agency playbook for AI-built client sites that don't look AI: using AI to build a client site is completely legitimate. I do it. Good studios do it. The leverage is real and refusing to use it is just expensive pride. The fraud isn't the tool. The fraud is charging custom-build rates for an unedited generator export and calling it "hand-coded." A studio that generates a first pass in Lovable and then spends real hours killing the tells — re-typesetting, re-coloring to a hue nobody else owns, breaking the three identical cards apart, ripping out data-v0-t, deleting the badge instead of hiding it — that studio earned its fee. The work is in the *removal*. This agency had skipped the only part that justified the price.
Marcus was quiet, then asked the question every founder asks: "Does it matter? It looks fine. It's live. Customers won't open DevTools."
Fair. So I gave him the part that wasn't aesthetic. One: he'd paid custom rates for a templated asset, which is a refund conversation whether or not the site works. Two: the site was indistinguishable from his competitors' — three of whom, I'd bet, shipped the same blue-500 hero — so it bought him zero differentiation in a market where differentiation was the entire point of having a site. Three, and this is the one that landed: prospects who *can* read the tells are exactly the technical buyers his B2B tool needed most, and a procurement lead who spots an AI-generated site in thirty seconds quietly downgrades their trust in everything behind it. If the site is generic, the reasoning goes, maybe the product is too. You don't get told you failed that test. You just don't get the meeting.
How it resolved
Marcus sent the agency the same six-minute video I'd sent him, with one line: "Walk me through the custom work I paid for." They offered a partial refund within a day — which tells you they knew. He took it, and used the money to have the site rebuilt properly: a real typeface with a personality, an accent color at a hue angle none of his competitors owned, a hero composition that wasn't centered-headline-over-gradient, three feature cards that were actually three *different* shapes because the three features were actually different. Same AI tools in the pipeline, used honestly. The data-v0-t is gone. There's no commented-out badge, because there's nothing to hide.
The lesson I took, and the one I gave Marcus, is smaller than "AI bad" and more useful. AI-built isn't the accusation. *Unfinished* is. A generator gets you to a 70-percent-generic first draft in ninety seconds, and the entire value a real builder adds now lives in the last 30 percent — the deliberate, un-defaultable choices that a model will never reach for because the model reaches for the average of everything it's seen. The hashed Inter, the #3b82f6, the three identical cards, the orphaned data-v0-t, the ghost badge: every one of those is a place where someone could have made a decision and didn't. The tells aren't proof that a machine was involved. They're proof that no human finished the job.
If you've just taken delivery of a "custom" site, do this before you celebrate: open DevTools, click your headline, read the computed font-family. If it says __Inter anything, click your main button and read the background-color. If that converts to #3b82f6, open View Source and search the document for data-v0, lovable, and Made with. The whole audit takes ninety seconds — the same ninety seconds it took to generate the site — and it'll tell you, with receipts, whether you paid for a decision or for a default. For the deeper version of the same forensics, the 23 tells that reveal an LLM wrote the code is the field guide I keep open in the other tab while I do it.
Marcus's site is genuinely good now. It cost him the original twelve thousand, minus a refund, plus a rebuild — an expensive way to learn that "custom" is a claim you're allowed to verify. The verification is free. The not-verifying is what costs twelve grand.
SHIP CODE THAT LOOKS INTENTIONAL
Scan your frontend for AI patterns. Generate a unique design system. Stop shipping the same blue gradient as everyone else.