Case study

eleven

A design system and a three-language website for a barbershop in Burgas, built so the whole site ships as one 8.5kB script and a visitor can book a chair without picking up the phone.

Client
eleven barberstudio, Burgas
Role
Design system, website
Scope
Tokens, motion, booking, performance
Year
2026
Visit the site

01The problem

Every booking arrived mid haircut

The shop had a logo and an Instagram account. It did not have a website. Every appointment was made by phone, and the phone rang while there were hands in somebody’s hair.

That costs twice. The barber stops cutting to answer, and the caller who does not get through goes to whoever picks up. Nothing on the internet could take a booking.

02Research

Where the customers actually come from

We looked at how people reach the shop today: a Google Maps listing, an Instagram profile, and word of mouth. Nearly all of it on a phone, most of it in the evening, a good part of it in summer when the town fills up.

Two facts came out of that and shaped everything after. The traffic is mobile on a seaside connection in August, so weight is a constraint rather than an audit at the end. And the customers here read Bulgarian, English and Russian, so the site has to as well.

03Empathize

Two people, and only one of them is browsing

The visitor is deciding, not researching. They want to know that the cuts are good and that they can get a slot on Thursday. They will give the page a few seconds on a bad connection, and they will not call to ask.

The barber is the second user, and he is holding clippers. Anything that needs his attention during the day is a failure. So the site had to take a booking without him, and stay correct without him editing it.

04Define

One job, stated plainly

Somebody in Burgas finds the shop on a phone and books a chair without calling. Everything else on the page is in service of that, and anything that was not got cut.

Three constraints came with it: it loads fast on a phone, it runs in three languages, and it looks like a shop worth sitting in. The last one is why there is a design system at all.

05Ideate

The brief came before the CSS

The design system was written as a document before it existed as a stylesheet. Colours, type scale, spacing, radii, worked up from a Cron Calendar style reference into a brief the build then followed literally. The first six lines of the stylesheet say so. Nothing in there is a value somebody eyeballed at two in the morning.

The wordmark was the only thing that already existed. The dark canvas, the ember accent and the whole token set are ours. Ember paints the booking button and the mark, and nothing else on the site is allowed to be orange, because one accent used once is what makes a single button read as the thing to press.

The other idea out of this stage is the opening. Instead of putting the wordmark on the page, put the page behind the wordmark, and let the mark fly up and become the navigation.

Color

Seven colors, one of them chromatic

Typography

Inter, and a display scale that fights back

Inter

Everything

400, 500, 700

Latin and Cyrillic out of one family, because the site runs in three languages and the headline has to set in all of them.

ТВОЯТ БРЪСНАР В БУРГАС

ABCDEFGHIJKLM NOPQRSTUVWXYZ abcdefghijklm nopqrstuvwxyz 0123456789

Design system

The parts, live

Rendered here in the client’s own colours, in the browser, not photographed. Cards at 0px, buttons at 4px, pills at 9999px, everything on a 4px unit, page capped at 1200px.

Запази час

The booking dock

Услуги и цени

Ghost link, section nav

Подстригване15 €
Подстригване и брада20 €
Оформяне на брада10 €

Price row

4+ години опит

The one eyebrow on the site, and it counts itself.

Caption, 13px, tracked

1px dots, 22px apart, at hairline alpha. It reads as grain on the surface rather than as pattern, which is the only reason it earns a place in the system.

Dot field, the one texture

Graphite on obsidian

Cards keep square corners. Only buttons round.

Surface and hairline

The opening reveal

Looking through the wordmark

The site scrolled top to bottom on a phone.

The mark is the aperture

The letterforms are an SVG mask over a photograph of the shop. Nothing is layered on top of anything. You are looking through the wordmark.

Zoom alone goes black

The centre of the wordmark is the gap between two letters, so past a certain scale the frame is looking through empty space. A rect fades up under the mask while the last letterform is still sweeping across, and hands the frame over.

It resolves, it does not just end

The scale curve is pow(q, 2.2) rather than linear. That holds the wordmark legible through the first third of the track, then accelerates hard, so the trip reads as a reveal instead of a zoom.

It lands as the nav

The flight to the header is measured, not guessed, and re-measured on resize, on orientation change, and on fonts.ready. The header mark is held at zero opacity and crossfades in over the last 6% of the flight, so there is never a second wordmark on screen.

Motion

The animation chases the scroll, it is not bound to it

Three tall tracks with sticky stages: the opening, the barber slice, the closing film. Scroll progress inside a track maps onto the animation, and everything runs from one damped requestAnimationFrame loop.

The loop moves the animation toward the scroll position instead of onto it, cur += (target - cur) * 0.16. Bound one to one, a flick on a phone snaps the reveal to its end frame and reads like dragging a slider. Chased, the same flick carries weight and settles. That is the entire feel of the page and it is one line.

Motion also finishes before the track does. The progress curve compresses the animation into the first 82% of the track, so the last stretch of scroll is a beat on a finished frame instead of dead travel. It is what stops a long reveal feeling long. The easing is in and out rather than ease out, which front loads the trip: two thirds of the distance goes in the first quarter and the wordmark is off the top of the frame before you have read it.

The barber slice is one photograph, clipped open from the top while it settles out of a slow push in. The version before it shredded the picture into fourteen sliding bands, and because each band carried its own residual offset the seams never landed flush. The photograph read as torn. A single element cannot tear.

The site

Section by section, in three languages

The barber slice: one photograph clipping open beside a short introduction
The barber slice. One photograph, clipped open from the top.
The price list: the services, each with a price and a length
The price list, with the length of each appointment.
The photo grid on the landing page, five black and white photographs
Five photographs as a selection, not a catalogue.
The full gallery page, the whole set of cuts on one page
The whole set lives on its own page, in all three languages.
The contact section: address, phone number, opening hours and a map link
Address, hours and phone, all read out of the one content file.
The FAQ section, questions and answers about booking, payment and walk-ins
The FAQ doubles as the FAQPage structured data.
The closing film: video of the shop scaling under a darkening veil
The closing film, decoding only while it is on screen.
The Russian hero, headline set in Cyrillic
Russian at /ru/. Bulgarian sits at the root, English at /en/.

Booking

Ten seconds down to 335 milliseconds

Booking is a Cal.com panel that opens over the page, not a new tab. Losing the visitor mid decision is the expensive part of a booking flow, and people mostly do not come back from a stray tab.

The Cal.com booking panel open over the site on a phone, listing the four services with their lengths, in the site's own dark palette

The panel opens over the page in the site’s own colours, already warm.

The problem

The panel took about ten seconds to open on a phone. The embed is 22kB and the thing behind it is a whole calendar application.

The fix that did nothing

We warmed the embed on pointerenter and touchstart, which is fine on a desktop and worth nothing on a phone, because touchstart lands a few milliseconds ahead of the click. The visitor pressed the button and then watched an empty panel while the embed, the calendar and a month of availability downloaded from scratch.

What worked

A timing change, not a size one. The embed warms on a MutationObserver watching the booking dock appear. The dock arrives when the opening reveal resolves, which is a scroll and a read away from anyone reaching for it, and that gap is the head start. Anyone who leaves during the opening never pays for any of it. A preconnect to app.cal.com takes DNS, TCP and TLS off the critical path.

Measured

On Fast 3G with 4x CPU throttling, the panel is on screen at 335ms and the calendar is usable at 1.6s, with the embed’s requests already finished before the tap.

Three smaller calls

Cal is handed the site’s own accent, so the panel does not arrive as a white sheet with somebody else’s blue in it. The button stays a real link to the booking page throughout, so if the script never runs, or Cal is blocked, or anything throws, the click is an ordinary navigation and the booking still happens. And there is one booking button on the page, the floating dock, and one only. A second one went into the closing section once and came straight back out.

06Test

Three things that shipped wrong first

The headline walked into the header. Sized off viewport width alone, the display clamp grew to the full height of a 16:9 laptop and collided with the nav, which is how it shipped once. The fix is a vh term inside the clamp, and a habit of testing at 1920x841 rather than 1440x900.

The photograph read as torn. An earlier version of the barber slice shredded the picture into fourteen sliding bands, and because each band carried its own residual offset the seams never landed flush. It is now one element clipped open from the top. A single element cannot tear.

The booking panel took ten seconds, which is its own section above, because the fix was a timing change rather than a size one.

With motion turned off, prefers-reduced-motion skips the loop and paints the same end state. Copy outside a track is revealed by CSS scoped to a js class, so nothing is trapped invisible when JavaScript does not run, and the language list only collapses once the script runs, so a visit without JavaScript keeps all three languages reachable.

Checked on every pass, in all three languages: exactly one h1, the heading line counts, the booking button count, no horizontal overflow from 360px to 1920px, console errors, and both JSON-LD blocks.

Performance

Small on purpose, from the first commit

8.5kB

JavaScript, whole site

335ms

to the booking panel on Fast 3G

1.6s

to a usable calendar

3

languages, one content file

0

framework runtime

20kB

gallery tiles, down from 190kB

3 days

brief to live, eleven commits

0

layout shift, mobile and desktop

Four hundreds, twice

Lighthouse 12, live siteMobileDesktop
Performance100100
Accessibility100100
Best practices100100
SEO100100
Largest contentful paint3.0s0.9s
Total blocking time0ms0ms
Cumulative layout shift00

One JavaScript file, 8,511 bytes, for the entire site. No framework runtime, no hydration, no client side router. One stylesheet at 22kB. The built Bulgarian home page is 50kB of HTML.

Every image is AVIF, quality 62 on the grids and 82 on the two full bleed photographs, generated at build into 75 variants. The JPG masters stay in the repo. That took the gallery tiles from around 190kB to under 20kB each.

Blocking time is zero and layout shift is zero on both form factors, which is what 8.5kB and fixed aspect ratios buy you. Nothing on the page moves after it paints, and there is no framework waiting to hydrate before it responds.

Performance, accessibility, best practices and SEO all come back at 100, on mobile and on desktop. That is not a tuning pass at the end. It is what is left when the site ships one script, sets an aspect ratio on every image, and renders its text as text.

The other honest number: the closing video is 2.8MB, the single largest thing in a 6.1MB build. It sits below the fold behind an IntersectionObserver, so it costs nothing on first paint. It is still 2.8MB.

07Implement

Astro 5, static, three days

Astro 5, static output, deployed on Vercel from main. The dependency list is astro, the sitemap integration and sharp. There is no framework runtime on the page because there is no framework on the page. Bulgarian sits at the root, English and Russian under /en/ and /ru/, on Astro’s own routing.

One content module is the only place anything factual is written down: all three languages, the address, the phone, the hours, the prices, the links. The pages, the structured data and the footer all read from it, so a fact changes once and changes in six places. Six indexable URLs, with canonical and hreflang built off one path value, so the gallery pages point at each other rather than at the home page.

Every photograph is graded to black and white in the file rather than with a CSS filter, by a small sharp script, so it is the same picture everywhere it is used. Images ship as AVIF. Caching is a year and immutable on the hashed assets, with the same headers written a second time in the format other hosts read, so the site can change hosts without quietly losing them.

Eleven commits, three days, 28 to 30 August 2026.

In the end

A barbershop that books itself.

One accent on near black, a wordmark that opens the page and then becomes the navigation, and a booking panel that is already warm by the time anyone reaches for it. The shop stopped taking bookings mid haircut, which was the whole reason to build it.

Let’s talk

Want a site this fast?

Let's talk