The cards were white boxes with a grey drop shadow and no edge — the default card of every web app,
sitting on a page that is otherwise cream, sage and a hand-drawn vine. Four changes, all of them things
the brand already owns:
- A WARM HAIRLINE. A card now has an edge, so it reads as stock lying on a counter rather than as a panel
floating over a screen.
- A SHADOW IN SAGE, not black — rgba(35,43,30,…), the darkest colour in the palette. Grey shadows are what
made them feel borrowed.
- A CREAM HEAD (`panel-head`). On a catering card the size, price and "About 6–8 people" sit on the tint
with the contents below, the way a printed menu separates what it costs from what you get. Product cards
use the same band for their caption, so the photo reads as mounted on the card instead of being it.
- THE BRANCH, at watermark weight, in the corner of every price card. It is the one mark that is
unmistakably this shop, and it costs nothing: the mask was already there for the wordmark.
The lift is a lift now, two pixels, rather than the shadow just darkening.
TWO THINGS TAILWIND v4 MADE ME FIX ALONG THE WAY. `.mark`, `.mark-l`, `.mark-r` and `.no-scrollbar` were
plain classes in @layer components, and only a registered utility can be @applied by another rule — the
watermark builds on the branch mark, so they became @utility like everything else here. And rewriting that
file dropped the `summary { list-style: none }` reset, which would have put a disclosure triangle beside
the hamburger on every phone; caught it by grepping the compiled CSS rather than by luck.
53 tests green, every class on every page resolves, and the price cards are in the screenshot.
TYPOGRAPHY, taken from the lockup and walked down to something you can read a paragraph in. "The Vine" in
LeJour Script over COFFEEHOUSE + BAKERY in AdBhashitha is the whole brand voice, so: the script stays in
the wordmark and nowhere else (it is unreadable at length); AdBhashitha carries h1–h4 and the price, and
keeps the wordmark's 0.01em letter-spacing so a heading sits on the same rhythm as the logo above it;
`eyebrow` and `label` are the hinge — sans, but letterspaced like the tagline — and then plain Raleway.
Sizes are declared once in type.css instead of being re-chosen per template, and none of the type classes
set a colour, because the same heading appears in sage on cream and in cream on sage.
BUTTONS AND SURFACES have names now. `panel` is the white box that was "bg-white rounded-3xl shadow-xs"
written out in nine places (which is how two of them end up with different corners); `panel-lift` is one
that reacts. `pill` is one shape in four colourways, which exist because this site puts buttons on cream
AND on sage and they cannot be the same colour on both. `chip`/`chip-on` are the products filter.
RHYTHM: `section` (py-16 md:py-24) and `band` (py-14 md:py-20, tighter because a coloured strip is already
doing the separating) replace nine hand-picked paddings.
FOCUS: one visible ring for everything reachable by keyboard, as an outline rather than a shadow so it
works on cream, on white and on the sage bands, at zero specificity so a utility can still win.
Z-INDEX: written down in base.css, all two of it — 40 the sticky header, 30 the mobile panel, which is a
child of the header and so can only ever sit under the bar. Everything else stacks in document order. The
point of the comment is that a third layer doesn't get invented at 9999.
DROPDOWNS: the admin's category selects were drawing the platform's own arrow in the platform's grey; they
get the site's chevron and room for it.
53 tests green. Every class used across all nine pages — five public, four admin — resolves in the
compiled stylesheet, so nothing here is styled by a name that doesn't exist.
MARKETING, not decoration — each of these was a page that stopped:
- The HOMEPAGE never mentioned catering. A customer found the page by reading the nav. It now has a
section with a photo, what the three tables cover, and a button.
- PRODUCTS was a title and a grid. It gets a line saying what it is ("cakes and decorated cookies are
made to order, so most of what follows started as somebody describing what they wanted") and a closing
band with somewhere to go — an order, or the catering page.
- CONTACT was a form and nothing else. The things somebody on that page actually wants — when we're open,
where we are, the phone number — were on the homepage and in the footer but not there. Now beside the
form, with a card pointing at catering.
- OUR STORY got the shop's own front as a band and an ending, so the story leads somewhere.
- The FOOTER gained the opening hours, which is what people come to a bakery's site for and is now on
every page.
CLEANED UP ON THE WAY. The hours and the address were about to exist in three places, so they are one
fragment (fragments/visit.html) that takes the classes that vary by where it sits — cream in the footer,
sage on white in a card. A shop that changes its Saturday hours should change them once.
STYLES MOVED to src/main/styles, with the rest of the app's source, and split by what each file is for:
theme.css (the fonts and the palette — names, nothing drawn), base.css (bare elements), components.css
(the site's own classes), admin.css (the admin's controls), and site.css as the entry that imports them in
cascade order and declares what Tailwind scans. It was one grab-bag file called tokens.css in a folder at
the repo root. node_modules has to stay beside them — Tailwind resolves `@import "tailwindcss"` by walking
up from the CSS file — so the whole npm project moved together, and the pom, .gitignore and README moved
with it.
53 tests green, `mvn package` builds the stylesheet from its new home, and every class used on all five
pages resolves in the compiled CSS (185/159/175/148/140, none missing).