Kelbin · template design language
The specification, and the gap between built and good.
Twenty-one homepages and twenty chat pages are live at the pack. This document is what the pack cannot tell you: the mechanisms underneath it, each written so it can be tested rather than admired.
1 · Why this document exists
The first template built from this design — Morrow Salon — got the visible things right and lost the invisible ones. That pattern is the reason for this document, so it is worth being precise about rather than polite.
What it got right: Fraunces with Figtree, which is the correct warm-editorial
pairing. The documented grounds and ink. Oxblood as the accent. A dedicated chat page at
/chat rather than a corner drawer. Those are real and they are not small.
What it lost, measured:
| Defect | Measured | Rule that would have caught it |
|---|---|---|
| Eyebrow collapses at 390px | 0px wide × 625px tall — one letter per line, stable at 3s | Mobile is the design surface |
| Every image 404s | 5 of 5 assets missing; page degrades to broken icons and alt text | No layout may depend on an image |
| Stock photography of people | alt text reads “Pexels demo image of a salon stylist” | Ban list · stock team photography |
| Banned face as the base | body { font-family: Inter, … }; html unset, falls to Times |
Only the four pairings |
| Type scale abandoned | 78, 60, 58, 56, 16, 14 — six of eleven sizes off-ramp | The documented scale |
| Context does not travel | 7 chat links, all bare /chat, zero seeded intent |
The chat contract |
Every surviving element was one you can see in a screenshot. Every lost element was a mechanism you can only know by being told: the scale, the image-independence, the seeding contract, the mobile-first rule.
That is a failure of the design to communicate, not of the build to care. A mock shows what a page looks like when everything goes right. It cannot show what must remain true when the photos never arrive, the business name is sixty characters, and nobody wrote the copy. This document exists to carry those.
2 · The locked system
Three axes flex per business: the accent family, the type register, and image density. Everything else is frozen. That is what lets a plumber and a florist each look right while sharing one standard.
Colour
| Token | Value | Role |
|---|---|---|
--ground | #FBF8F3 | Page ground |
--ground-raised | #F4EFE7 | Quiet fills, image slots |
--ground-inverse | #1A1714 | Inverse surfaces |
--ink | #1A1714 | Text — 16.85 on cream, 15.59 on bone |
--ink-muted | #6B6259 | Secondary text — 5.64 / 5.22 |
--ink-muted-on-dark | #A79E93 | Secondary on ink — 6.76 |
--rule / --rule-soft | #E5DDD0 / #EFE8DC | Hairlines |
Fourteen candidates were tested against cream and bone. Not one passed as body text on
both. A single --accent token is broken by construction: a colour dark enough
to read on cream is too dark to read on ink.
Terracotta #B65335 / #C96445 · Olive #6A7740 / #859550 ·
Ink Blue #4475A7 / #5185B8 · Oxblood #C24766 / #C95E79 ·
Teal #3C7C7A / #4B9B98. One family per business, drawn from their own brand —
never a Kelbin accent, because the accent is how sites differ from each other.
Test: every colour that renders resolves to a documented token or an alpha derivative of one; every text colour clears 4.5:1 against its composited background, not its declared one.
Type
The typographic system is invariant; the typefaces are not. Faces flex as complete pairings, tested together — a law firm and a Vietnamese restaurant should not share a body face, and unrestricted choice produces mismatched pairs.
| Register | Display + Body | Suits | Used by |
|---|---|---|---|
| Warm editorial | Fraunces + Figtree | salons, restaurants, wellness, hospitality | salon, cafe, r-menu, v-desk2, v-open, m-editorial |
| Clinical | Source Serif 4 + Source Sans 3 | clinics, legal, professional services | clinic, accountant, r-diag, v-desk, m-ledger |
| Modern | Instrument Serif + Instrument Sans | studios, creative, modern retail | wedding, r-poster, v-stage, v-ambient, m-stage |
| Sturdy | Lora + Libre Franklin | trades, mechanics, local practical services | plumber, r-fp, v-input, m-signpost |
Scale, ratio 1.333: 72 · 54 · 40 · 30 · 23 display, then 17 body, 15 label. Body copy is 17px / 1.6 / 62–72ch in every pairing — that measure, not the face, is the invariant. Nothing renders below 15px, which supersedes the document's own 13px caption step for customer-facing pages: the reader is outdoors on a phone.
Morrow rendered 78, 60, 58, 56, 16 and 14. Each looks defensible alone. Together they are the difference between a typographic system and a pile of font sizes, and it is the single most reliable tell of a template that was eyeballed rather than built.
Test: collect the computed font-size of every element
containing text; the set must be a subset of {72, 54, 40, 30, 23, 17, 15}.
Space, radius, motion
Space — 4 · 8 · 12 · 16 · 24 · 32 · 48 · 64 · 96 · 128 · 160. Section padding 96
mobile, 160 desktop. Whitespace is the luxury signal: fewer, bigger moments.
Radius — 0 / 2 / 4 / 8, hard maximum 8, images 4; heavy rounding is an era marker.
Motion — 160ms, opacity and transform only, honour
prefers-reduced-motion, and no scroll reveals: they already read dated.
3 · The block registry
Every block in the pack, its idiom, and what it does when the business cannot supply its content. A block that has no honest empty state is not finished.
| Block | Idiom | Still works without |
|---|---|---|
svc — services | Named service, plain price, description that sells by being specific | Prices — every row swaps to “On quote”. Used priceless by plumber, wedding, signpost |
price-ledger | Price and its condition on the same row; the condition is the honesty | Any row; prices swap to On quote |
facts — hours + location | Hours as a table, address spoken like directions | An email — many trades are phone-only. Never assumes both |
slot — image | Neutral --ground-raised block, thin rule, label naming the intended shot | Itself. Removing every slot must leave a composed page |
triage | The customer names their problem before the business names itself | —; practical-posture only |
avail | Real bookable times as the hero; honest state or omit | Live data — omit the block entirely rather than fake slots |
board | Open/closed now, because that is what a café is asked | Live data — degrade to the hours table |
menu | Typeset print: dotted leaders, rules, no boxes, no table markup | Prices; not the items |
cond — conditions | Problems in the customer's words, not the trade's service names | — |
steps | Numbered process; each step says what it buys you | — |
plans | Comparison, not a list — you pick which one you are | Prices; the columns still compare inclusions |
dates | Deadlines a small business actually misses | — |
area | A radius, for a trade with no address worth visiting | An address entirely |
callout | What it costs to get someone there, before the work is priced | — |
capacity | Scarcity stated as plain fact, never as urgency | — |
diary | Free slots are links; taken slots are not clickable | Live data — omit rather than invent |
dock / chips | Commodity composer; identity lives in the chips and the voice around it | Chips — the composer alone still works |
doorway | Persistent “Ask Kelbin” that navigates. One per page, never a panel | — |
thread / carried / composer | The chat page: what travelled in, the exchange, the reply field | carried when the doorway was used and no intent was named |
Master pages additionally declare blocks explicitly as data-block with a
data-kit annotation carrying idiom, registry correspondence and
still-works-without. Open any master with ?kit=1 to read them in place.
.slot and every image from any page. Each remaining
section must still be over 40px tall and contain text. No section may become an empty band —
which means an image never gets a section to itself; it sits beside content.
4 · The four masters
A master is a block sequence plus composition rules. Four exist, and the greyscale wireframe test confirms they stay distinguishable with all dress removed — distinct sequences and distinct hero blocks.
| Master | Sequence | Composition rule |
|---|---|---|
| Set-back editorial | hero-setback › services-list › chat-entries › hours-contact | Type leads. Imagery is contained below the words, never behind them. Air is the point |
| Image stage | hero-stage › services-band › chat-entries › hours-contact | Full-bleed image with a computed scrim; the name stands on the picture. The stage falls back to the ink field when no image exists |
| Signpost | hero-signpost › what-list › chat-entries › hours-contact | The name set enormous is the hero. Zero photography as a first-class path — not even a slot |
| Ledger | hero-ledger › price-ledger › chat-entries › hours-contact | Dense and priced. Contact in the masthead, before any scroll. Every rate carries its condition |
Test: render all four at ?wire=1. No two may share a block
sequence or a hero block. If a pair becomes indistinguishable, they are one master — merge
them and report it, rather than shipping two names for one thing.
5 · Dress: a theme is tokens, not a rebuild
A dress re-points tokens over an unchanged structure. The structure must not know which dress it wears.
data-dress="dark"— grounds and ink invert to the bone palette, and--accentre-points to--accent-on-dark. A deliberate register choice for a specific business.data-dress="bw"— the accent abdicates:--accent: var(--ink). Ink does the pointing.
Dark-mode-by-default is on the ban list because it fails our customers' customers, who are on phones in daylight. The dark dress is a chosen register for a business whose room is actually dark. When classification runs at scale, a classifier must never route a business to the dark dress as a fallback.
Test: the two dress files render byte-identical DOMs apart from the
data-dress attribute — verify by diff and by comparing
outerHTML in the browser, since a build step can diverge what the source
shares.
6 · The chat contract
This is the part most likely to be built as a shell and lose its purpose, so it is specified exactly.
It hands work back to the customer at the exact moment we promised to take work away. If a button's label is a verb with a known object — Book a chair, Change my hours — it either performs the action or opens the chat with that sentence already sent and answered. Only Ask, Tell, Talk to earn a blank box.
| Parameter | Carries | Behaviour on arrival |
|---|---|---|
?i=<n> | A named intent from the page | Renders the sent message, Kelbin's reply, and the subject chips that travelled with it |
?q=<text> | Free text composed in a dock | Renders that text verbatim as the sent message, with a reply that moves it forward |
&slot=<label> | A specific diary slot taken | Prepends that slot to the carried chips |
?ask=1 | The doorway — no intent named | The only legitimate blank composer; no sent turn is rendered |
&y=<px> | Where the customer was standing | “Back to site” restores that exact scroll position |
Restoring scroll before the webfonts settle clamps to a shorter page and lands you near
but not where you were. Re-apply on document.fonts.ready and
load. Landing at the top of the homepage is the single most likely way this
pattern turns from a signature into a tax.
Test: scroll well down, take an entry, press Back to site, and assert the scroll position is within 30px of where it left.
The chat is always its own page with its own URL — never a modal, drawer or corner panel. A page is returnable, linkable and resumable, which is what matters the moment Kelbin takes a booking and someone asks “what time did I book?” tomorrow, on another device.
7 · Standing rules, with their tests
Each rule is paired with the check that proves it. A rule without a test is a preference.
| Rule | Test |
|---|---|
| No layout may depend on an image for legibility. Type-led is the default; photography is enhancement | Render every page with images removed. It must still read as composed, with no empty sections |
| Text over an image requires a computed scrim — never white-on-photo by hope | Sample mean luminance inside the text's bounding box; fail below 4.5:1 |
| Every text slot declares a length range and the layout holds all of it | Render with a ~60-character business name and a 3-character one. No overflow, no overlap, no clipping |
| Exactly one primary action, persistent on mobile | At 390px the primary action is reachable without scrolling, on every page |
| No numeric proof slot without a data source | Generate with no stats supplied: no empty slots, no invented figures |
| The site must never state a fact the business did not supply | Diff every factual claim on the page against the supplied profile. Anything unsupported is a defect, not a flourish |
| Mobile is the design surface, not the adaptation | Every page reviewed at 390px before desktop; no sideways scroll, and no element flush against the screen edge |
| Nothing below 15px; tap targets at least 44px | Computed font-size and bounding-box height across every text node and control |
| Alt text is derived, never invented | If we do not know what is in the photograph, we do not describe it |
Name, phone and location on every page — not only in the footer — plus LocalBusiness schema |
Assert a tel: link and the business name outside the footer on every page |
Never
Indigo or violet default accent · Inter · Space Grotesk · gradient hero · glassmorphism · 3D decoration · three columns of circle icons · stock team photography · rounded-everything · dark mode by default · lorem ipsum · invented statistics, testimonials, review counts, awards or years-in-business.
Published commercial templates ship fabricated proof as standard — invented review counts, testimonials from people who do not exist, and in one case literal “Logoipsum” placeholders left visible in the live design. A generator handed an empty proof slot will fill it. Do not create the slot.
8 · What separates built from 10/10
Not taste. Six concrete things, drawn from what the first build lost.
- It survives its own missing assets. The photos will not arrive. A 10/10 template looks deliberate with every image removed; a built one shows holes. Test it by deleting them, not by imagining it.
- The type scale is a closed set. Seven sizes, no others. The moment a seventh becomes a ninth, the page stops reading as designed and starts reading as assembled.
- Every entry point carries its subject. A chat link that arrives knowing “Thursday 5:30, cut and half-head colour, about two hours” is a different product from one that arrives blank. This is the whole proposition, and it is one URL parameter.
- Mobile is composed, not survived. 390px is where the customer's customer actually is. A hero that is beautiful at 1280 and collapses at 390 is not 8/10, it is broken.
- The copy is the business's own — and this one is testable too. Keep the owner's plain sentence; do not upgrade a plumber into marketing prose. That rewriting is exactly how a site stops sounding like the business, which is the proposition. Apply the substitution test below.
- Honest state or no state. “Next free chair 5:15” makes a sparse page feel alive; the same line invented makes the whole site a lie within a week. Bind it to real data or omit the block.
The substitution test
Every line must be a fact about this business that could not be true of another business in the same trade. Swap in a competitor's name. If the sentence still works, it is decoration — cut it.
| Fails — true of any salon | Passes — true only of this one |
|---|---|
| “Make room for your next good hair day” | “Unhurried hair, on Lygon Street.” |
| “A better appointment starts with a little clarity” | “A four-chair studio. One stylist, one guest at a time.” |
| “Detail carries the mood” | “Steph keeps the diary small on purpose.” |
Composed, curated, considered, elevated, mood, clarity, detail, craft, thoughtful, intentional, seamless, bespoke. These are words for talking about design, not for a salon talking to someone who needs a haircut.
And no sentence may describe the website itself. The first production build shipped “The website stays composed even when the photo count does not” as customer copy on a salon page — a rule from the design document rendered as marketing. The customer does not know there is a template, and should not be told.
No internal identifiers on a customer surface, ever. A live site was found serving
T-036 in its page title and again in its chat heading. Template ids, site ids
and build references never reach rendered text, titles, alt attributes or metadata.
Test: for each headline and subhead, substitute a competitor's name and
ask whether the sentence survives; it must not. Then grep the rendered text for the banned
vocabulary, for any sentence whose subject is the site, and for
/[A-Z]-?\d{3}/-shaped identifiers. All three must return nothing.
9 · Findings you inherit
Recorded so they are not rediscovered by someone building on them.
- Some registers are conditional on live data. The poster, the open-line and the split-stage only stay honest if their state is generated. As static copy they rot into a lie within a week.
- The ambient variant is the weakest under the imageless constraint. It adds least over the front desk and is the variant that most needs the photography the constraint bans. If one is cut, cut that one.
- Single input is right for practical trades and wrong for atmospheric ones. It should never be offered to a florist or a wedding planner.
- The first-person register cannot be literally seamless. Making the page-to-chat shift imperceptible would require Kelbin to impersonate the owner, which the honesty rule forbids. The resolution is the page introducing Kelbin in the owner's own words, so the handover is explicit while the register never shifts.
- Five accent families, six categories. One family is deliberately used twice, in different registers and postures. Six distinct families do not exist and inventing a sixth is not the fix.
- The corpus is aimed wrong. 6,114 of 6,116 references come from one designer marketplace whose audience is agencies and portfolios. Not one reviewed reference sits in a Kelbin vertical, and trades have 38. No amount of curation finds what was never captured.
Questions on any of this — ask Sana. The pack is static and self-contained: HTML, one stylesheet, no framework, no build step, no dependency beyond Google Fonts.