Building a Pirate-Themed Barbershop Site, Mostly by Prompting Claude
A barber in Cove, Utah had an Instagram and no website. I gave Claude the right prompt, all the real details, and a styling direction, and it built the three files. The hard part was getting the content, not the code.
Max runs Pirate Barber out of Cove, Utah. I found him the way most people do: his Instagram, @pirate.barber. Good cuts, a real local following, and no website. If you searched his shop, you got a handful of social posts and nothing that told you what he charged, when he was open, or how to book. So I reached out and offered to fix that.
This is the third of these I’ve done now, after a renovation company and a 40-year-old hair salon. The pattern is the same: a small local business that lives on word of mouth and a social profile, and needs one honest page on the internet. What’s different about this one is how little of it I actually typed.
What the thing is
Pirate Barber is a single-page site. A dark hero with the shop name, a short bit about Max, three service cards, and a contact section with location, phone, hours, and an Instagram link. That’s the whole thing. It’s live at piratebarber.vientapps.com.
Under the hood it is three files: one HTML, one CSS, one JS. No framework, no build step, no dependencies. The same call I keep making for sites this size, because it loads instantly and there is nothing to break later.
How I worked with Claude on this one
I’ll be straight about the split: Claude wrote almost all of it. This was not a pair-programming session where I drove and it autocompleted. I gave Claude Code the business details, the page structure I wanted, and a clear styling direction, the dark pirate identity, gold accents, a serif that feels engraved, and let it build the files. Two commits, a couple hours apart, and the site was done.
My actual job was upstream of the code. Get the real information. Decide the look. Write the prompt well enough that Claude had something to aim at. When the prompt and the inputs were good, the output was good. When I was vague, I got vague back. That’s the honest version of “I built this with AI”: the leverage was real, but it lived in the setup, not the typing.
The stack, and why it’s boring on purpose
Vanilla HTML, CSS, and JavaScript. The CSS is one file built around custom properties, so the entire theme lives in a single block at the top. Claude set this up, and it’s the kind of structure that makes the whole palette adjustable from one place:
:root {
--color-bg: #0a0a0a;
--color-bg-alt: #1a1a1a;
--color-gold: #c9a84c;
--color-gold-light: #ddc06e;
--color-red: #8b1a1a;
--color-text: #e8e8e8;
--font-heading: 'Cinzel', Georgia, serif;
--font-body: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
}
Near-black base, antique gold for highlights, a deep red I barely use, and Cinzel for the headings to get that aged, engraved feel. The body stays on a plain system font so the page weighs almost nothing.
Beyond the three files, the repo carries a robots.txt, a sitemap.xml, and a Netlify _headers file for security headers. Claude added the SEO pieces in the second pass when I asked it to tighten things up.
The hard part wasn’t the code
The code was the easy part. The hard part was content.
An Instagram profile tells you a business exists and roughly what it feels like. It does not tell you that a haircut is $35, a shave is $20, a kids cut is $20, or that the shop is closed Mondays and open till seven Thursday through Saturday. It doesn’t hand you the story of who Max is in a form you can put on an About section. All of that had to come out of conversation and then get confirmed, because the one thing worse than no website is a website with the wrong hours.
This is the same wall I hit on the salon site. The build is a half-day. The content is the part that takes real back-and-forth, and there’s no shortcut Claude can take for you, because Claude does not know what Max charges.
Where Claude surprised me
The accessibility. I did not ask for it.
The mobile nav is 35 lines of JavaScript, and Claude wrote it with the details most people skip on a small marketing site: the toggle updates aria-expanded, the menu closes on the Escape key, and focus returns to the toggle button afterward so a keyboard user isn’t dumped somewhere random.
// Close mobile menu on Escape key
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && menu && menu.classList.contains('is-open')) {
menu.classList.remove('is-open');
toggle.setAttribute('aria-expanded', 'false');
toggle.focus();
}
});
That toggle.focus() is the tell. Nobody asked for it, it doesn’t show up in a screenshot, and it’s the right thing to do. Claude treated a three-file barbershop site like it deserved the same care as a real app, which is more than I’d have bothered with by hand on a job this small.
It even paired the JS with a CSS-only hamburger-to-X animation driven entirely off that same aria attribute, so the open state and the icon never drift apart:
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(1) {
transform: translateY(7px) rotate(45deg);
}
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(2) {
opacity: 0;
}
.nav__toggle[aria-expanded="true"] .nav__toggle-bar:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
The accessibility state is the single source of truth, and the visual just follows it. No extra JS to keep the icon in sync.
Where Claude fell short
It was generic until I pushed it.
The first pass was competent and forgettable. It looked like a clean template for any local business: fine spacing, sensible sections, zero personality. Nothing about it said pirate, said barber, said this specific shop. Left to its defaults, Claude plays it safe, and safe here meant interchangeable.
What fixed it was giving it something real to react to. I pointed it at an actual high-end barbershop site as a reference (there’s still a WebFetch permission for masterbarbersla.com sitting in the project’s .claude settings from that session), and told it to lean into the name. Once it had a concrete target instead of “make it look nice,” the dark-and-gold identity showed up and the page stopped looking like a template. The lesson I keep relearning: Claude is much better at “make it look like that” than “make it look good.”
Claude also added a Schema.org BarberShop block on its own initiative, which is genuinely useful for local search:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BarberShop",
"name": "Pirate Barber",
"telephone": "+14357645900",
"priceRange": "$$"
}
</script>
Worth knowing it does this, because the JSON-LD has to match reality. It’s another place where Claude will confidently fill in structure, and you’re the one who has to make sure the phone number and hours inside it are actually correct.
What else went wrong
The honest gap is photos. There are none. The hero is a pure CSS gradient because there was nothing to put there, and the Open Graph tags point at an og-image.jpg that doesn’t exist on the live site yet. The gradient carries it, but a real shot of Max in the chair would do more for trust than any amount of styling. That’s on me, not Claude.
Where it stands
It’s live and it does the job. Someone can find Pirate Barber, see the services and prices, check the hours, and tap to call, all from their phone. For a shop that previously existed only as an Instagram handle, that’s the whole point.
What I’d do differently
Get the content first, in one structured pass. Same lesson as last time, still not fully learned. Before I write a prompt or a line of code, I should send a short questionnaire: services with prices, exact hours, the owner’s story, payment methods, and photos. Gather it once instead of pulling it out in pieces while the build waits.
Insist on real photos before launch. The site works without them, but it would be better with them, and “we’ll add photos later” tends to mean never. Next client, photos are a launch requirement, not a nice-to-have.
Hand Claude a reference up front, not after the bland draft. The generic first pass wasn’t Claude’s fault, it was mine for not anchoring the style early. Give it a concrete visual target in the first prompt and skip the round where it plays it safe.
Related: Pirate Barber and more side projects.
Frequently Asked Questions
Can Claude build a full small-business website from one prompt?
What did Claude do well on this build?
Where did Claude fall short?
Why use plain HTML and CSS instead of a framework?
What is the hardest part of building a site for a business that only exists on Instagram?
Founder of Vient and senior staff engineer
Caden Sorenson runs Vient, an independent studio building iOS apps, web tools, and client websites, including Travel Vient, a travel research site with everything cited to primary sources. He's a senior staff engineer with 15+ years of experience building iOS apps, web platforms, and developer tools, and a Computer Science graduate from Utah State University. Based in Logan, Utah.
Related posts
- Building a Website for a 40-Year-Old Hair SalonA local salon in Smithfield, Utah had been cutting hair for four decades without a website. Here's how I built one from scratch with vanilla HTML, CSS, and JS.
- Building a Layover Calculator That Knows Every Terminal at JFKHow I built a connection time calculator covering 70 airports with pairwise terminal transfers, customs buffers, and a five-factor assessment algorithm.
- I Let Claude Design My Homepage Hero and Shipped What It BuiltI gave Claude Design six 'decide for me' answers and it came back with a three-layer canvas flight animation. Two concepts, one conversation.
Built as part of
View the project →